Spring Cache

572 阅读5分钟

简介

Spring Cache 是Spring - context-xxx.jar中提供的功能,可以结合EHCache,Redis等缓存工具使用。给用户提供非常方便的缓存处理,缓存基本判断等操作,可以直接使用注解实现。

在包含了Spring - context-xxx.jar的Spring Boot项目中,在启动类中添加@EnableCaching注解,即可开启缓存功能。默认Spring Cache是不开启

加载缓存工具顺序

只要检测到项目中配置了下面缓存工具。(导入了依赖,在Spring容器中发现对应工具的内容),无论导入多少个缓存工具用于只用最前面的一个。

默认寻找缓存工具的顺序:(为什么redis配置上就可以用的原因)

  1. Generic

  2. JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)

  3. EhCache 2.x

  4. Hazelcast

  5. Infinispan

  6. Couchbase

  7. Redis

  8. Caffeine

  9. Simple

无参使用

  • pom.xml添加依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.7.RELEASE</version>
    </parent>
    <dependencies>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
    
  • 新建配置文件application.yml

    spring:
      redis:
        host: 192.168.8.129
    #    cluster:
    #      nodes: 集群时使用
    #    port: 6379
    
  • 新建启动类

    @SpringBootApplication
    @EnableCaching
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class,args);
        }
    }
    
  • 新建Service以及实现类

    • 每个方法单独配置key前缀适用于整个类中方法的前缀不统一的情况。

    • @Cacheable表示要对方法返回值进行缓存。缓存时key的名称为cacheName::key

    • cacheNames : 配置key的前缀

    • key:配置key的后缀。里面字符串要使用单引号

    • Spring Cache使用的RedisTemplate<StringRedisSerializer,JdkSerializationRedisSerializer>

      public interface DemoService {
          String demo();
      }
      
      @Service
      public class DemoServiceImpl implements DemoService {
          @Override
          // 固定字符串需要使用单引号
          @Cacheable(key = "'demo'",cacheNames = "com.sxt")
          public String demo() {
              System.out.println("demo方法被执行");
              return "demo-result";
          }
      }
      
    • 统一配置类中方法前缀

      public interface DemoService {
          String demo();
      }
      
      @Service
      @CacheConfig(cacheNames = "com.sxt")
      public class DemoServiceImpl implements DemoService {
          @Override
          // 固定字符串需要使用单引号
          @Cacheable(key = "'demo'")
          public String demo() {
              System.out.println("demo方法被执行");
              return "demo-result";
          }
      }
      
    • 新建控制器

      @Controller
      public class DemoConteroller {
          @Autowired
          private DemoService demoService;
          @RequestMapping("/demo")
          @ResponseBody
          public String demo(){
              return demoService.demo();
          }
      }
      

带有参数的方法缓存

  • 在@Cacheable的key属性中通过#参数名可以获取到方法参数。key中内容Spring EL,既然是表达式字符串要用单引号,没有被单引号包含的内容都表示变量。

  • 注意:基本上当方法有参数时,设置key的时候需要添加上参数条件。因为参数不一样,方法的返回值也可以不一样了。

    @Service
    @CacheConfig(cacheNames = "com.bjsxt")
    public class DemoServiceImpl implements DemoService {
        @Override
        // 固定字符串需要使用单引号
        @Cacheable(key = "'demo'")
        public String demo() {
            System.out.println("demo方法被执行");
            return "demo-result";
        }
        @Override
        // Spring EL
        // 字符串使用单引号
        // #+方法参数名称:可以调用方法参数
        @Cacheable(key = "'selectById'+#id")
        public String selectById(Long id) {
            System.out.println("执行了selectById:"+id);
            return "selectById"+id;
        }
    }
    

返回值为对象或集合

  • 这个时候需要更换Redis中Value对应的序列化器。更换序列化器后同时也会解决redis中数据前面出现乱码的问题。因为默认对Redis的value序列化器使用JdkSerializationRedisSerializer序列化器。

  • 如果不更换也可以有效果, 但是实体类必须实现Serializable接口

  • 新建配置类 ,注意:此配置方法影响是的Spring Cache,当Spring Cache使用Redis作为缓存工具时,key和value的序列化类型。如果项目中需要使用Spring Data Redis直接操作Redis,下面配置方法对操作时的key和value的序列化类型没有影响,还是默认的JDK序列化器,如果需要更改成GenericJackson2JsonRedisSeriablizer,还需要编写之前RedisTemplate实例配置方法

    @Configuration
    public class RedisConfig {
    
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
            //缓存配置对象
            RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
    
            redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) //设置缓存的默认超时时间:30分钟
                    .disableCachingNullValues()             //如果是空值,不缓存
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))         //设置key序列化器
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));  //设置value序列化器
    
            return RedisCacheManager
                    .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                    .cacheDefaults(redisCacheConfiguration).build();
        }
    }
    
  • Spring Data Redis直接操作Redis , 默认对Redis的value序列化器使用JdkSerializationRedisSerializer序列化器。

    @Configuration
    public class RedisConfig {    
       @Bean  
       public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){  
          RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();        
          redisTemplate.setKeySerializer(new StringRedisSerializer()); 
        // 一定要保证自己操作redis时value的序列化器类型和Spring Cache的value序列化器类型一样。        
         redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());              redisTemplate.setConnectionFactory(factory);       
           return redisTemplate;    
       }
    }
    
  • 编写service

    @Override
    @Cacheable(key = "'selectByUsername'+#id+':'+#username")
    public People selectByUsername(Long id,String username) {
        People people = new People();
        people.setName(username);
        people.setId(id);
        return people;
    }
    
    @Override
    @Cacheable(key = "'selectAll'")
    public List<People> selectAll() {
        List<People> list = new ArrayList<>();
        list.add(new People(1l,"张三"));
        list.add(new People(2l,"李四"));
        return list;
    }
    
  • 编写控制器

    @RequestMapping("selectByUsername")
    public People selectByUsername(Long id,String username){
        return demoService.selectByUsername(id,username);
    }
    
    @RequestMapping("/selectAll")
    public List<People> selectAll(){
        return demoService.selectAll();
    }
    

condition和unless属性

@Cacheable中的属性

  • condition是普通条件。如果条件成立则进行缓存。此判断为进入到方法体之前的判断,所以#result不允许用在这里。如果在condition中出现#result会导致条件恒不成立,不进行缓存。

  • unless是方法执行完成后的条件,当符合条件不被缓存。注意:#result只能写在这个属性中。多用在返回结果为null时不缓存效果。

  • 演示为参数不等于5时才被缓存

    @Override
    @Cacheable(key = "'selectById2'+#id",condition = "#id!=5")
    public People selectById2(Long id) {
        System.out.println("执行selectById2");
        return null;
    }
    
  • 演示当方法返回值为null时不被缓存。

    @Cacheable(key = "'selectById2'+#id",unless= "#result == null ")
    public String selectById2(Long id){
        System.out.println("执行selectById2");
        return null;
    }
    

删除缓存

使用@CacheEvict进行删除

  • 可以删除固定的key的缓存数据

    @CacheEvict(key = "'demo'")
    @Override
    public void delete() {
        System.out.println("执行delete");
    }
    
  • 可以通过参数实现通过删除方法

    方法参数key值表示要删除Redis中com.sxt::key这样的数据

    @CacheEvict(key = "#key")
    @Override
    public int delete(String key) {
        System.out.println("执行了delete方法");
        return 0;
    }
    

修改缓存

@CachePut和@Cacheable的区别

  • @Cacheable 如果发现有缓存数据,直接获取缓存数据。如果没有缓存数据,执行方法,把方法返回值进行缓存。

  • @CachePut 无论是否已经缓存,恒执行方法体,每次都会把方法返回值数据缓存到缓存工具中。根据@CachePut的特性,多用这个注解实现缓存修改。

    // Cache 每次都走方法体,无论redis中是否存在指定key.
    // 恒执行redis的新增操作
    @CachePut(key = "'update'")
    @Override
    public String update() {
        System.out.println("执行修改");
        return "update2";
    }
    

Spring Cache 的执行原理

  1. Spring Cache扫描容器中包含了RedisCacheManager实例。将会使用Redis作为缓存工具。

  2. 在执行业务方法时,如果方法上包含@Cacheable将会判断是否存在key,如果存在key直接获取Redis中数据作为方法返回结果。如果不存在key则执行方法后,把方法返回值缓存到Redis中,最后返回返回值。

  3. 如果执行业务方法时,方法上面包含@CacheEvit将会在执行完成方法后删除缓存数据。

  4. 如果执业务方法时,方法上面包含@CachePut,每次都会执行方法体,并且把方法返回值缓存到Redis中。