Spring-Boot-Cache

Spring Boot与缓存


JSR107

Spring缓存抽象

Spring简化了JSR107的操作,让我们可以更加轻松的开发

引入依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

使用注释@EnableCaching开启缓存

cacheManager默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache,将数据保存在ConcurrentMap<Object, Object>

配置:

  1. 自动配置类;CacheAutoConfiguration
  2. 缓存的配置类
    • org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
    • org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration[默认]
    • org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

@Cacheable

然后在需要缓存的方法上@Cacheable来进行缓存

1
2
3
4
5
@Cacheable(cacheNames = "employee", condition = "#id>0", unless = "#result == null")
public Employee getEmpById(Integer id){
logger.info("query the employee whose id equal to " + id);
return employeeMapper.getEmpById(id);
}

注意一定要有cacheNames来指定使用至少一个cache,如果cacheManager中没有这个cache,就会自动创建,默认的key是方法参数的值,value是方法的返回值,到第二次通过相同的参数使用这个方法时,就会在cache中抽取而不调用方法。

可以指定key或keyGenerator来自定义自己的key名,key名支持SpEL,例如要是使用上面的id的值,可以#id来指定参数名为id的参数,也可以#a0,#p0,#root.args[0]来指定第一个参数

也可以使用condition或unless来判断,只有满足情况才缓存,如上所示

使用sync来指定是否使用异步模式,异步模式下不能使用unless

@Cacheable主要的参数

名称 解释
value/cacheNames 缓存的名称,在 spring 配置文件中定义,必须指定至少一个。@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则按照方法的所有参数进行组合。 @Cacheable(value=”testcache”,key=”#id”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false, 只有为 true 才进行缓存@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
unless 否定缓存。当条件结果为TRUE时,就不会缓存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”)
cacheManager/cacheResolver 指定使用哪一个cacheManager(一般是自定义的)

SpEL上下文数据

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname#root.method.name
method root对象 当前被调用的方法 #root.method
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]#a0#p0
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result
  1. 方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
    • (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
  2. 去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
    • key是按照某种策略生成的,默认是使用keyGenerator生成的
    • 默认的keyGenerator是SimpleKeyGenerator
    • SimpleKeyGenerator生成key的默认策略:
      • 如果没有参数:key=SimpleKey.Empty,即key=new SimpleKey(),一个空的SimplyKey对象
      • 如果只有一个参数:key=param,这个参数值
      • 如果有多个参数:key=new SimpleKey(params),创建一个SimplyKey对象存所有的参数
  3. 没有查到缓存就调用目标方法
  4. 将目标方法返回的结果,放进缓存中

@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存,以后再来调用就可以直接使用缓存中的数据。因此@Cacheablekey属性不能使用#result,因为在方法调用前就要确定key的值。

@CachePut

既调用方法,又更新缓存。与Cacheable不同的是,它会先调用方法,然后把方法的返回值放进缓存中,参数与@Cacheable类似,不同的是key的值可以使用#result

1
2
3
4
5
6
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
logger.info("update the employee's info of :"+employee);
employeeMapper.updateEmp(employee);
return employee;
}

@CacheEvict

用于删除缓存,相比@Cacheable而言多了下面2个属性

名称 解释
allEntries 是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法执行前就清空,缺省为 false,如果指定为 true, 则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法 执行抛出异常,则不会清空缓存@CachEvict(value=”testcache”,beforeInvocation=true)

@Caching

@Caching用于定义复杂的缓存规则,例如要同时使用多个注释组合时可以:

1
2
3
4
5
6
7
8
9
10
11
12
@Caching(
cacheable = {
@Cacheable(value="emp",key = "#lastName")
},
put = {
@CachePut(value="emp",key = "#result.id"),
@CachePut(value="emp",key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}

表示以lastName为key放入缓存的同时分别把以id和email为key的缓存也放入缓存。但是因为这里使用了@CachePut,所以无论缓存中有没有以lastName为key的记录都会调用方法。

@CacheConfig

用于配置当前类下的所有Cache注释的公共属性

1
@CacheConfig(cacheNames="emp",cacheManager = "employeeCacheManager")

然后在类里的Cache注释,就可以省略公共属性

整合Redis

一如既往:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后设置Redis的位置(这里我是在阿里云服务器上使用docker建了一个redis映射到6380端口)

1
2
3
4
spring:
redis:
host: 112.74.42.31
port: 6380

注意redis的key和value都需要序列化,要给bean实现一个Serializable接口,否则会弹出无法序列化的错误。若要直接操作redis,可以让Spring自动注入一个redisTemplate来进行操作

1
2
@Autowired
RedisTemplate redisTemplate;

如果要自定义redis配置(例如定义序列化和反序列化格式),可以给容器中添加一个RedisTemplateRedisCacheManager的bean。如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {

RedisTemplate<Object, Employee> template
= new RedisTemplate<Object, Employee>();

template.setConnectionFactory(redisConnectionFactory);

Jackson2JsonRedisSerializer<Employee> serializer
= new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(serializer);
return template;
}
1
2
3
4
5
6
7
8
9
10
11
@Bean
public RedisCacheManager employeeCacheManager(
RedisTemplate<Object, Employee> empRedisTemplate){
RedisCacheManager cacheManager
= new RedisCacheManager(empRedisTemplate);

//使用前缀,默认会将CacheName作为key的前缀
cacheManager.setUsePrefix(true);

return cacheManager;
}

如果要使用这个cacheManager,可以在使用缓存的类中用cacheManager属性指定。