SpringCache

JSR107

Java Caching 定义了5个核心接口 分别是 CachingProvider,CachManager、Cache、Entry、Expiry。

  • CachingProvider 定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期间访问多CachingProvider。
  • CacheManger 定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache 存在于CacheManager 的上下文中,一个CacheManager 仅被一个CacheingProvider 所拥有。
  • Cache 是一个类似Map的数据结构并临时存储以key 为索引的值。一个Cache 仅被一个CacheManager 所拥有
  • Entry 是一个存储在Cache中的key-value对
  • Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个有效期,条目变为过期状态。一旦过期,条目将不可访问、更新、和删除。缓存有效期可以通过 ExpiryPolicy 设置。
image.png

开发中使用JSR-107需要导入包:一般不直接使用JSR-107开发,因为JSR-107仅仅定义了接口,而没有实现

<!-- https://mvnrepository.com/artifact/javax.cache/cache-api -->
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.1.0</version>
</dependency>

Spring缓存抽象

Spring 从3.1 开始支持 JSR-107 注解 简化我们开发

  • org.springframework.cache.Cache
  • org.springframework.cache.CacheManager
  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache、EhCacheCache、ConcurrentMapCache等;


    image.png

执行原理:每次调用需要缓存功能的方法时,Spring会检查指定参数的目标方法是否已经被调用过,如果有直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果返回给用户。下次调用直接从缓存中获取。

使用Spring缓存抽象时我们需要关注以下两点:

  1. 确定方法需要被缓存以及他们都缓存策略
  2. 从缓存中读取之前都缓存存储都数据

Spring 缓存开发重要概念

Spring 缓存开发相关概念 作用功能
Cache 缓存接口,定义缓存操作。实现有 如RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法都请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存
@EnableCaching 开启基于注解的缓存
KeyGenerator 缓存数据时Key生成策略
serilalize 缓存数据时value序列化策略

SpringBoot 整合 Spring缓存抽象

基于 spring-boot-starter 2.4.0
springCache官方文档:https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/integration.html#cache

搭建Mybatis crud 环境(略)

  1. 导入相关 pom文件
  <!--    Spring 缓存    -->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
   </dependency>
  1. 开启基于缓存的注解 @EnableCaching

  2. 标注缓存注解 @Cacheable(cacheNames = {"xxx"}

    @Cacheable(cacheNames = {"stu"})
    @Override
    public Student getStu(Integer id) {
        Student student = studentMapper.selectByPrimaryKey(id);
        System.out.println(student);
        return student;
    }

如果只标注 @Cacheable 会报异常: At least one cache should be provided per cache operation.

@Cacheable

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {

    @AliasFor("cacheNames")
    String[] value() default {};

    @AliasFor("value")
    String[] cacheNames() default {};

    String key() default "";

    String cacheManager() default "";

    String cacheResolver() default "";

    String condition() default "";

    String unless() default "";

    boolean sync() default false;

cacheNames/value :指定缓存组件对名字;CacheManager 管理多个Cache 组件,每一个缓存组件有自己唯一一个名字

@Cacheable(cacheNames = {"stu"})
    @Override
    public Student getStu(Integer id) {
        Student student = studentMapper.selectByPrimaryKey(id);
        System.out.println(student);
        return student;
    }

key:缓存数据的 key,默认是使用方法的参数的值。

比如:使用方法名字getStu和方法参数拼接 key:getStu[1],编写SpEl:#id; 参数id的值 #a0 #p0 # root.args[0]

    @Cacheable(cacheNames = {"stu"}, key = "#root.methodName+'['+#id+']'")
    @Override
    public Student getStu(Integer id) {
        Student student = studentMapper.selectByPrimaryKey(id);
        System.out.println(student);
        return student;
    }

debug模式下可以看下如下结果:


image.png
image.png

keyGenerator:key的生成器,可以自己指定key的生成器的组件id

编写配置类

@Component(value = "MyKeyGenerator")
public class MyKeyGenerator implements KeyGenerator{
    @Override
    public Object generate(Object target, Method method, Object... params) {
       return  method.getName()+"["+ Arrays.asList(params).toString()+"]";
    }
}

keyGenerator = "MyKeyGenerator"

    @Cacheable(cacheNames = {"stu"}, keyGenerator = "MyKeyGenerator")
    @Override
    public Student getStu(Integer id) {
        Student student = studentMapper.selectByPrimaryKey(id);
        System.out.println(student);
        return student;
    }

cacheManager:指定缓存管理器

cacheResolver:指定缓存解析器

condition :指定符合条件的情况下才缓存

id为1时才缓存

    @Cacheable(cacheNames = {"student"}, keyGenerator = "MyKeyGenerator", condition = "#a0 == 1")
    @Override
    public Student getStu(Integer id) {
        Student student = studentMapper.selectByPrimaryKey(id);
        System.out.println(student);
        return student;
    }

unless :否定缓存:当unless 指定的条件为true,方法的返回值就不会被缓存;

    @Cacheable(cacheNames = {"stu"}, keyGenerator = "MyKeyGenerator", condition = "#a0 == 1", unless = "#a0 == 1")
    @Override
    public Student getStu(Integer id) {
        Student student = studentMapper.selectByPrimaryKey(id);
        System.out.println(student);
        return student;
    }

注意:满足condition条件 又满足 unless 是不缓存的 unless 优先

sync: 是否使用异步模式默认是false 如果开启为true 则unless 属性就不能得到支持

可以获取 到结果进行判断 unless = “#result == null”

@CachePut

既调用方法又更新缓存。修改了数据库某个数据,同时更新缓存

  1. 先调用目标方法
  2. 将目标方法的结果缓存起来
    @CachePut注解中的属性和 @Cacheable中的属性几乎是一样的就不在展开
    @CachePut(value = "stu", key = "#student.id")
    @Override
    public Student updateStu(Student student){
        System.out.println(student);
        studentMapper.updateByPrimaryKey(student);
        return student;
    }

key还可以写成 key = "#result.id",因为先调用方法然后把结果缓存起来,所以可以拿到结果取出id

注意: 一定要指定key 不然会按默认值方法的参数 新生成一个 k,并把结果缓存

@CacheEvict

allEntries 默认 false 是否清空所有缓存
beforeInvocation 默认 false 缓存清除操作在方法之后执行,如果方法异常,则缓存不会清除

@CacheEvict(value = "#id", allEntries = true, beforeInvocation = true)
    public void delSut(Integer id) {
        System.out.println(id);
        studentMapper.deleteByPrimaryKey(id);
    }

@Caching

组合多个Cache注解使用

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

    Cacheable[] cacheable() default {};

    CachePut[] put() default {};

    CacheEvict[] evict() default {};

}

例子:

    @Caching(
            cacheable = {@Cacheable(value = "stu",key = "#userName")},
            put = {@CachePut(value = "stu", key = "#result.id"),
                    @CachePut(value = "stu", key = "#result.age")
            }
    )
    public Student getStuByStr(String userName) {
        StudentExample studentExample = new StudentExample();
        studentExample.createCriteria().andUserNameEqualTo(userName);
        List<Student> students = studentMapper.selectByExample(studentExample);
        return Optional.ofNullable(students).orElse(null).get(0);
    }
@CacheConfig 抽取缓存的公共配置

我们每个缓存注解中 都指定 了value = "stu" / cacheNames="stu" ,可以抽离出来,在整个类上添加
@CacheConfig(cacheNames = "stu"),之后每个方法中都默认使用 cacheNames = “stu”

@CacheConfig(cacheNames = "stu")
@Service
public class StudentServiceImpl implements StudentService {

    @Resource
    private StudentMapper studentMapper;

    @CachePut(key = "#result.id")
    @Override
    public Student updateStu(Student student){
        System.out.println(student);
        studentMapper.updateByPrimaryKey(student);
        return student;
    }
    /**
     * Cacheable
     * @param id
     * @return
     *
     * key = "#root.methodName+'['+#id+']'"
     */
    @Cacheable(key = "#id")
    @Override
    public Student getStu(Integer id) {
        Student student = studentMapper.selectByPrimaryKey(id);
        System.out.println(student);
        return student;
    }

    @CacheEvict(allEntries = true, beforeInvocation = true)
    public void delSut(Integer id) {
        System.out.println(id);
        studentMapper.deleteByPrimaryKey(id);
    }

    @Caching(
            cacheable = {@Cacheable(key = "#userName")},
            put = {@CachePut(key = "#result.id"),
                    @CachePut(key = "#result.age")
            }
    )
    public Student getStuByStr(String userName) {
        StudentExample studentExample = new StudentExample();
        studentExample.createCriteria().andUserNameEqualTo(userName);
        List<Student> students = studentMapper.selectByExample(studentExample);
        return Optional.ofNullable(students).orElse(null).get(0);
    }
}

Cache SpEL available metadata

名字 位置 描述 实例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0],#a0,#p0
caches root对象 当前方法调用使用的缓存列表 如 如@Cacheable(value={"cache1", "cache2"})),则有两个cache #root.caches[0].name
Argument Name 执行上下文evaluator context 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; #artsian.id #a0 #p0
result 执行上下文evaluator context 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351