前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring 全家桶之 Spring Boot 2.6.4( Ⅰ )- Caching(Part B)

Spring 全家桶之 Spring Boot 2.6.4( Ⅰ )- Caching(Part B)

作者头像
RiemannHypothesis
发布2022-09-26 16:01:05
2830
发布2022-09-26 16:01:05
举报
文章被收录于专栏:Elixir

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情

一、Spring Cache Abstraction

Spring Cache 的使用

@Cacheable 运行流程

CacheAutoConfiguration是缓存的自动配置类

image.png
image.png

自动配置类使用@Import注解导入CacheConfigurationImportSelector类

image.png
image.png

这个类会往容器中导入一些缓存组件

image.png
image.png

打上断点,开启Debug模式

image.png
image.png

往容器中导入了10个组件,都是各种缓存配置类,导入的这些类都会有一些规则

image.png
image.png

在application.yml增加配置,打印出容器中的类,看到底哪些缓存配置类生效了

代码语言:javascript
复制
debug: true
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

只有SimpleCacheConfiguration是生效的,其他的都没有匹配上

image.png
image.png

而SimpleCacheConfiguration通过@Bean注解往容器中导入了一个ConcurrentMapCacheManager

image.png
image.png

ConcurrentMapCacheManager实现了CacheManager接口

image.png
image.png

在getCache()方法打断点,查看如何根据缓存名字获取缓存

image.png
image.png

缓存是存在Map中的,是一个ConcurrentHashMap

image.png
image.png

getCache()方法可以根据缓存名字获取缓存,如果根据缓存名字查询不到缓存就通过createConcurrentMapCache()方法创建一个缓存并返回

image.png
image.png

createConcurrentMapCache()方法通过new关键字创建了ConcurrentMapCache

image.png
image.png

ConcurrentMapCache作用就是将缓存的存储在CurrentMap中,其实就是一个Map,该类中包含了一个lookup方法用来从CurrentMap中查询缓存以及put()方法将缓存保存在CurrentMap中,底层是调用了Map的get()方法和put()方法

image.png
image.png

在目标方法、lookup()、put()以及ConCurrentMapCacheManager类上的getCache()方法上打断点,重新启动Debug查看@Cacheable注解的运行流程

在浏览器发送请求 /tesla/1166057520

之后会先获取name为tesla的缓存

image.png
image.png

继续Step Over, 获取到的缓存为null,这是就会创建name为tesla的缓存

image.png
image.png

接着点击左下侧的Resume Program,跳到下一个断点既lookup()

image.png
image.png

这个key就是查询传入的参数,在前面生成的

image.png
image.png
image.png
image.png

回到ConcurrentMapCache中继续Debug

image.png
image.png

进入到put()方法中,此时已经调用了目标方法,并且拿到了返回值

image.png
image.png

更换查询的数据,在TeslaMapperImpl的getTeslaById()方法上打断点,重新Debug

image.png
image.png

跳转至下一个断点就来到put()方法,该方法将查询到的结果存储到CurrentMap中

image.png
image.png

@Cacheable运行流程总结:

  1. 方法运行之前,先查询Cache,按照注解中定义的CacheNames指定的名称来获取缓存,第一次获取缓存如果没有Cache组件会自动创建
  2. 去Cache中查询缓存的内容,使用一个key,这个key默认就是目标方法传递的参数,key是按照策略生成的,默认使用SimpleKeyGenerator生成,默认的策略为:
    • 如果没有参数,key = new SimpleKeyGenerator()
    • 如果有一个参数,key就是参数的值
    • 如果有多个参数,key = new SimpleKey(params)
  3. 如果查询不到缓存就去调用目标方法,使用生成的key来存储到Map中
  4. 将目标方法返回的结果放进缓存中

也就是说@Cacheable标注的方法执行之前都会先去缓存中查询有没有这个数据,默认按照目标方法传递的参数查询,如果没有就运行方法并将结果缓存,如果不是第一次运行就直接存缓存中获取数据,核心就是CacheManager按照cacheNames得到Cache

@Cacheable的属性
cacheNames/value

@Cacheable的cacheNames或者value属性指的是缓存组件的名字,将返回结果放在哪个缓存中,是一个数组,也就是说可以放在多个缓存中

key

key是用来存取缓存的,缓存是存储在Map中的,key非常重要,key可以使用SpEL表达式来指定也可自定义KeyGenerator来指定,也可以使用默认的SimpleKeyGenerator生成

使用SpELl表达式指定key为getTeslaById(1166057547)

代码语言:javascript
复制
@Cacheable(cacheNames = {"tesla"}, key = "#root.methodName+'(' + #id + ')'")

重新启动该应用,浏览器中执行查询操作

image.png
image.png

自定义KeyGenerator,实现KeyGenerator接口,并将该类注册到容器中

代码语言:javascript
复制
@Configuration
public class LilithCacheKeyGenerator {

    @Bean(lilithKeyGenerator)
    public KeyGenerator keyGenerator(){
        return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]";
    }
}

在TeslaServiceImpl类上的getTeslaById()方法上使用自定义的KeyGenerator,需要注意的是key和keyGenerator的使用只能二选一

代码语言:javascript
复制
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator")

在自定义的KeyGenerator类上打断点,开启Debug模式,在浏览器上执行查询操作

image.png
image.png

首先会来到getCache()方法

image.png
image.png

点击Run to Cursor到下一个断点就来到自定义的keyGenerator方法中

image.png
image.png

再到下一个断点,此时key的形式即为自定义的形式getTeslaById[1166057546]

image.png
image.png
condition

condition条件,只有condition指定的条件为true时才会缓存结果

代码语言:javascript
复制
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator", condition = "#id==1166057546")

这里指定的condition条件是只有id=1166057546才会缓存,否则不缓存

重启应用,在浏览器多次查询id=1166057546,控制台打印的日志中只会执行一次SQL

image.png
image.png

多次查询id=id=1166057547,会执行多次,查询结果不会被缓存,因为此时condition条件为true

image.png
image.png
unless

unless属性指定条件为true时,不会缓存返回结果

代码语言:javascript
复制
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator", unless = "#id==1166057546")

这里指定的unless是当查询1166057546既条件为true时,返回结果不会被缓存

重启应用,在浏览器执行查询操作,此时查询1166057546时,结果没有被缓存,unlesss条件为true

image.png
image.png

1166057547 如果查询的数据不是1166057546,查询结果会被缓存,因为unless条件为false

image.png
image.png

异步模式下不支持unless

@CachePut注解

既调用方法,有更新缓存数据,在修改了数据库的某个数据,同时更新缓存 在TeslaService增加updateTesla()方法,并在TeslaServiceImpl中实现

代码语言:javascript
复制
void updateTesla(Tesla tesla);
代码语言:javascript
复制
@Override
@CachePut(cacheNames = {"tesla"}, key = "#tesla.id")
public void updateTesla(Tesla tesla) {
    log.info(tesla.getId() + "被更新了");
    teslaMapper.update(tesla);
}

TeslaController中增加方法

代码语言:javascript
复制
@GetMapping("/tesla")
public void update(Tesla tesla){
    teslaService.updateTesla(tesla);
   
}

去除unless或者condition条件

代码语言:javascript
复制
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator"

重启应用,现在浏览器中查询1166057546,执行一次之后,返回结果被缓存

image.png
image.png

接着修改1166057546,/tesla?id=1166057546&name=TeslaRoadster&price=1600000&vehicleType=roaster&factoryId=3,修改也是成功的

image.png
image.png

再次查询1166057546

image.png
image.png

此时发现查询到的结果还是之前的结果,并不是更新后的结果,这是因为更新没有返回数据,方法中都是void,所以缓存中的数据没有变化

依次修改TeslaService接口、TeslaServiceImpl类以及TeslaController中的update方法,使方法返回更新后的数据

代码语言:javascript
复制
Tesla updateTesla(Tesla tesla);
代码语言:javascript
复制
@Override
@Cacheable(cacheNames = {"tesla"}, keyGenerator = "lilithKeyGenerator"
public Tesla updateTesla(Tesla tesla) {
    log.info(tesla.getId() + "被更新了");
    return tesla;
}
代码语言:javascript
复制
@GetMapping("/tesla")
public Tesla update(Tesla tesla){
    Tesla updateTesla = teslaService.updateTesla(tesla);
    return updateTesla;
}

重新启动应用,在浏览器中查询116605754

image.png
image.png

更新116605754

image.png
image.png

再次查询116605754

image.png
image.png

控制台返回的仍是第一次查询时保存的数据

这一次是因为查询结果存储时使用的key是id,更新结果存储时使用的key是employee对象,统一key 再次测试,都是用id作为key,只需要修改TeslaServiceImpl中的updateTesla()

代码语言:javascript
复制
@CachePut(cacheNames = {"tesla"}, key = "#tesla.id")

重启应用,再次查询

image.png
image.png

进行更新操作

image.png
image.png

再次查询

image.png
image.png

更新后再次查询时返回的数据是更新后的数据

使用@CachePut需要注意的事项:

  • 更新后要将更新的数据返回
  • 要将查询时保存的key和更新时设置的key保持一致,这样才会更新缓存
@CacheEvict

该注解的作用是用来清除缓存

分别在TeslaService接口、TeslaServiceImpl实现类以及TeslaController中增加方法

代码语言:javascript
复制
void deleteTeslaCache(Integer id);
代码语言:javascript
复制
@CacheEvict(cacheNames = {"tesla"}, key = "#id")
public void deleteTeslaCache(Integer id){
    log.info("清除" + id + "的缓存");
}
代码语言:javascript
复制
@GetMapping("/tesla/delcache/{id}")
public void deleteCache(@PathVariable("id") Integer id){
    teslaService.deleteTeslaCache(id);
}
image.png
image.png

/tesla/delcache/1166057546

image.png
image.png
image.png
image.png
allEntries属性

该属性是会删除缓存中所有的数据,默认是清除指定cacheNames的缓存

beforeInvocation属性

该属性是指在方法执行前删除缓存还是方法执行后删除缓存,为布尔值类型,默认为false既在方法执行后删除缓存

代码语言:javascript
复制
@CacheEvict(cacheNames = {"tesla"}, key = "#id")
public void deleteTeslaCache(Integer id){
    log.info("清除" + id + "的缓存");
    // 增加异常代码
    int i = 1 / 0;
}

重启

image.png
image.png
image.png
image.png
image.png
image.png

再次查询,没有调用SQL语句,方法由于出现异常,导致未能执行清除缓存

代码语言:javascript
复制
@CacheEvict(cacheNames = {"tesla"}, key = "#id", beforeInvocation = true)
image.png
image.png
image.png
image.png
image.png
image.png

再次查询还是执行了SQL语句,说明在方法执行前就已经将缓存删除,方法中的异常不会对清除缓存造成影响

@Caching

该注解用来指定多个复杂规则

image.png
image.png

分别在TeslaController、TeslaService、TeslaServiceImpl、TeslaMapper以及TeslaMapper.xml中增加代码及SQL语句,按照name来查询

代码语言:javascript
复制
@GetMapping("/tesla/name/{name}")
public Tesla findByName(@PathVariable("name") String name){
    return teslaService.getTeslaByName(name);
}
代码语言:javascript
复制
Tesla getTeslaByName(String name);
代码语言:javascript
复制
public Tesla getTeslaByName(String name){
    log.info("根据" + name + "查询特斯拉");
    return teslaMapper.selectOneByName(name);
}
代码语言:javascript
复制
Tesla selectOneByName(String name);
代码语言:javascript
复制
<select id="selectOneByName" resultType="tesla">
    SELECT <include refid="Base_Columns_List" />
    FROM tesla
    WHERE name = #{name}
</select>

在TeslaServiceImpl的getTeslaByName()方法上增加@Caching注解,该注解中配置了一个@Cacheable注解和一个@CachePut注解

代码语言:javascript
复制
@Caching(
        cacheable = {
            @Cacheable(cacheNames = "tesla", key = "#name")
        },
        put = {
            @CachePut(cacheNames = "tesla", key = "#result.id"),
        }
)

重新应用,首先按照name查询 /tesla/name/TeslaRoadster,控制台打印出SQL

image.png
image.png

接着按照id查询 /tesla/1166057546,此时已经有缓存了,没有执行SQL语句

image.png
image.png

再次按照name查询,控制台打印出SQL语句

image.png
image.png

再用name查询,还是会执行SQL,因为getTeslaByName()方法有@CachePut注解,这个注解就表示一定要执行方法

CacheConfig

@CacheConfig注解标注在类上,可以定义一些公共的缓存配置,比如cacheNames、key等

image.png
image.png

在TeslaServiceImpl类上增加@CacheConfig,配置cacheNames

代码语言:javascript
复制
@CacheConfig(cacheNames = "tesla")

将getTeslaByName()方法上@Caching标注的cacheNames属性删除

代码语言:javascript
复制
@Caching(
        cacheable = {
            @Cacheable(key = "#name")
        },
        put = {
            @CachePut(key = "#result.id"),
        }
)

重新启动应用,首先按照name查询/tesla/name/TeslaRoadster,控制台打印出SQL

image.png
image.png

接着按照id查询

image.png
image.png

再次按照name查询

image.png
image.png

控制台输出结果与单独配置cacheNames时输出的结果一致

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Spring Cache Abstraction
    • Spring Cache 的使用
      • @Cacheable 运行流程
      • @Cacheable的属性
      • @CachePut注解
      • @CacheEvict
      • @Caching
      • CacheConfig
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档