一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情。
CacheAutoConfiguration是缓存的自动配置类
自动配置类使用@Import注解导入CacheConfigurationImportSelector类
这个类会往容器中导入一些缓存组件
打上断点,开启Debug模式
往容器中导入了10个组件,都是各种缓存配置类,导入的这些类都会有一些规则
在application.yml增加配置,打印出容器中的类,看到底哪些缓存配置类生效了
debug: true
只有SimpleCacheConfiguration是生效的,其他的都没有匹配上
而SimpleCacheConfiguration通过@Bean注解往容器中导入了一个ConcurrentMapCacheManager
ConcurrentMapCacheManager实现了CacheManager接口
在getCache()方法打断点,查看如何根据缓存名字获取缓存
缓存是存在Map中的,是一个ConcurrentHashMap
getCache()方法可以根据缓存名字获取缓存,如果根据缓存名字查询不到缓存就通过createConcurrentMapCache()方法创建一个缓存并返回
createConcurrentMapCache()方法通过new关键字创建了ConcurrentMapCache
ConcurrentMapCache作用就是将缓存的存储在CurrentMap中,其实就是一个Map,该类中包含了一个lookup方法用来从CurrentMap中查询缓存以及put()方法将缓存保存在CurrentMap中,底层是调用了Map的get()方法和put()方法
在目标方法、lookup()、put()以及ConCurrentMapCacheManager类上的getCache()方法上打断点,重新启动Debug查看@Cacheable注解的运行流程
在浏览器发送请求 /tesla/1166057520
之后会先获取name为tesla的缓存
继续Step Over, 获取到的缓存为null,这是就会创建name为tesla的缓存
接着点击左下侧的Resume Program,跳到下一个断点既lookup()
这个key就是查询传入的参数,在前面生成的
回到ConcurrentMapCache中继续Debug
进入到put()方法中,此时已经调用了目标方法,并且拿到了返回值
更换查询的数据,在TeslaMapperImpl的getTeslaById()方法上打断点,重新Debug
跳转至下一个断点就来到put()方法,该方法将查询到的结果存储到CurrentMap中
@Cacheable运行流程总结:
也就是说@Cacheable标注的方法执行之前都会先去缓存中查询有没有这个数据,默认按照目标方法传递的参数查询,如果没有就运行方法并将结果缓存,如果不是第一次运行就直接存缓存中获取数据,核心就是CacheManager按照cacheNames得到Cache
@Cacheable的cacheNames或者value属性指的是缓存组件的名字,将返回结果放在哪个缓存中,是一个数组,也就是说可以放在多个缓存中
key是用来存取缓存的,缓存是存储在Map中的,key非常重要,key可以使用SpEL表达式来指定也可自定义KeyGenerator来指定,也可以使用默认的SimpleKeyGenerator生成
使用SpELl表达式指定key为getTeslaById(1166057547)
@Cacheable(cacheNames = {"tesla"}, key = "#root.methodName+'(' + #id + ')'")
重新启动该应用,浏览器中执行查询操作
自定义KeyGenerator,实现KeyGenerator接口,并将该类注册到容器中
@Configuration
public class LilithCacheKeyGenerator {
@Bean(lilithKeyGenerator)
public KeyGenerator keyGenerator(){
return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]";
}
}
在TeslaServiceImpl类上的getTeslaById()方法上使用自定义的KeyGenerator,需要注意的是key和keyGenerator的使用只能二选一
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator")
在自定义的KeyGenerator类上打断点,开启Debug模式,在浏览器上执行查询操作
首先会来到getCache()方法
点击Run to Cursor到下一个断点就来到自定义的keyGenerator方法中
再到下一个断点,此时key的形式即为自定义的形式getTeslaById[1166057546]
condition条件,只有condition指定的条件为true时才会缓存结果
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator", condition = "#id==1166057546")
这里指定的condition条件是只有id=1166057546才会缓存,否则不缓存
重启应用,在浏览器多次查询id=1166057546,控制台打印的日志中只会执行一次SQL
多次查询id=id=1166057547,会执行多次,查询结果不会被缓存,因为此时condition条件为true
unless属性指定条件为true时,不会缓存返回结果
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator", unless = "#id==1166057546")
这里指定的unless是当查询1166057546既条件为true时,返回结果不会被缓存
重启应用,在浏览器执行查询操作,此时查询1166057546时,结果没有被缓存,unlesss条件为true
1166057547 如果查询的数据不是1166057546,查询结果会被缓存,因为unless条件为false
异步模式下不支持unless
既调用方法,有更新缓存数据,在修改了数据库的某个数据,同时更新缓存 在TeslaService增加updateTesla()方法,并在TeslaServiceImpl中实现
void updateTesla(Tesla tesla);
@Override
@CachePut(cacheNames = {"tesla"}, key = "#tesla.id")
public void updateTesla(Tesla tesla) {
log.info(tesla.getId() + "被更新了");
teslaMapper.update(tesla);
}
TeslaController中增加方法
@GetMapping("/tesla")
public void update(Tesla tesla){
teslaService.updateTesla(tesla);
}
去除unless或者condition条件
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator"
重启应用,现在浏览器中查询1166057546,执行一次之后,返回结果被缓存
接着修改1166057546,/tesla?id=1166057546&name=TeslaRoadster&price=1600000&vehicleType=roaster&factoryId=3,修改也是成功的
再次查询1166057546
此时发现查询到的结果还是之前的结果,并不是更新后的结果,这是因为更新没有返回数据,方法中都是void,所以缓存中的数据没有变化
依次修改TeslaService接口、TeslaServiceImpl类以及TeslaController中的update方法,使方法返回更新后的数据
Tesla updateTesla(Tesla tesla);
@Override
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator"
public Tesla updateTesla(Tesla tesla) {
log.info(tesla.getId() + "被更新了");
return tesla;
}
@GetMapping("/tesla")
public Tesla update(Tesla tesla){
Tesla updateTesla = teslaService.updateTesla(tesla);
return updateTesla;
}
重新启动应用,在浏览器中查询116605754
更新116605754
再次查询116605754
控制台返回的仍是第一次查询时保存的数据
这一次是因为查询结果存储时使用的key是id,更新结果存储时使用的key是employee对象,统一key 再次测试,都是用id作为key,只需要修改TeslaServiceImpl中的updateTesla()
@CachePut(cacheNames = {"tesla"}, key = "#tesla.id")
重启应用,再次查询
进行更新操作
再次查询
更新后再次查询时返回的数据是更新后的数据
使用@CachePut需要注意的事项:
该注解的作用是用来清除缓存
分别在TeslaService接口、TeslaServiceImpl实现类以及TeslaController中增加方法
void deleteTeslaCache(Integer id);
@CacheEvict(cacheNames = {"tesla"}, key = "#id")
public void deleteTeslaCache(Integer id){
log.info("清除" + id + "的缓存");
}
@GetMapping("/tesla/delcache/{id}")
public void deleteCache(@PathVariable("id") Integer id){
teslaService.deleteTeslaCache(id);
}
/tesla/delcache/1166057546
该属性是会删除缓存中所有的数据,默认是清除指定cacheNames的缓存
该属性是指在方法执行前删除缓存还是方法执行后删除缓存,为布尔值类型,默认为false既在方法执行后删除缓存
@CacheEvict(cacheNames = {"tesla"}, key = "#id")
public void deleteTeslaCache(Integer id){
log.info("清除" + id + "的缓存");
// 增加异常代码
int i = 1 / 0;
}
重启
再次查询,没有调用SQL语句,方法由于出现异常,导致未能执行清除缓存
@CacheEvict(cacheNames = {"tesla"}, key = "#id", beforeInvocation = true)
再次查询还是执行了SQL语句,说明在方法执行前就已经将缓存删除,方法中的异常不会对清除缓存造成影响
该注解用来指定多个复杂规则
分别在TeslaController、TeslaService、TeslaServiceImpl、TeslaMapper以及TeslaMapper.xml中增加代码及SQL语句,按照name来查询
@GetMapping("/tesla/name/{name}")
public Tesla findByName(@PathVariable("name") String name){
return teslaService.getTeslaByName(name);
}
Tesla getTeslaByName(String name);
public Tesla getTeslaByName(String name){
log.info("根据" + name + "查询特斯拉");
return teslaMapper.selectOneByName(name);
}
Tesla selectOneByName(String name);
<select id="selectOneByName" resultType="tesla">
SELECT <include refid="Base_Columns_List" />
FROM tesla
WHERE name = #{name}
</select>
在TeslaServiceImpl的getTeslaByName()方法上增加@Caching注解,该注解中配置了一个@Cacheable注解和一个@CachePut注解
@Caching(
cacheable = {
@Cacheable(cacheNames = "tesla", key = "#name")
},
put = {
@CachePut(cacheNames = "tesla", key = "#result.id"),
}
)
重新应用,首先按照name查询 /tesla/name/TeslaRoadster,控制台打印出SQL
接着按照id查询 /tesla/1166057546,此时已经有缓存了,没有执行SQL语句
再次按照name查询,控制台打印出SQL语句
再用name查询,还是会执行SQL,因为getTeslaByName()方法有@CachePut注解,这个注解就表示一定要执行方法
@CacheConfig注解标注在类上,可以定义一些公共的缓存配置,比如cacheNames、key等
在TeslaServiceImpl类上增加@CacheConfig,配置cacheNames
@CacheConfig(cacheNames = "tesla")
将getTeslaByName()方法上@Caching标注的cacheNames属性删除
@Caching(
cacheable = {
@Cacheable(key = "#name")
},
put = {
@CachePut(key = "#result.id"),
}
)
重新启动应用,首先按照name查询/tesla/name/TeslaRoadster,控制台打印出SQL
接着按照id查询
再次按照name查询
控制台输出结果与单独配置cacheNames时输出的结果一致