欢迎来到信息岛!
adminAdmin  2024-11-06 15:52 信息岛 显示边栏 |   抢沙发  15 
文章评分 0 次,平均分 0.0

SpringBoot之缓存规范

JSR107

缓存规范

JSR是Java Specification Requests 的缩写 ,Java规范请求 , JSR-107就是关于如何使用缓存的规范。

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

1、CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个 CachingProvider。

2、CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager 的上下文中。一个CacheManager仅被一个CachingProvider所拥有。

3、Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。

4、Entry是一个存储在Cache中的key-value对。

5、Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条 目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

一个应用里面可以有多个缓存提供者(CachingProvider),一个缓存提供者可以获取到多个缓存管理器(CacheManager),一个缓存管理器管理着不同的缓存(Cache),缓存中是一个个的缓存键值对(Entry),每个entry都有一个有效期(Expiry)。缓存管理器和缓存之间的关系有点类似于数据库中连接池和连接的关系。

核心接口

  • CachingProvider 提供缓存理者,即CacheManager
  • CacheManager:缓存管理者
  • Cache:缓存
  • Entry:缓存中的键值对象
  • Expiry:缓存有效期

依赖

使用JSR-107需导入,SpringBoot项目已经内置引入了这个依赖

<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>

Spring缓存支持

三种缓存

  • 从 Spring 3.1 开始,Spring 定义了 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口,以统一不同的缓存技术。同时,Spring 还支持使用 JCache(JSR-107)注解,进一步简化了开发过程。

    Cache 接口定义了缓存组件的规范,包含了缓存的各种操作方法集合。

    Cache 接口的基础上,Spring 提供了多种 xxxCache 实现,例如:

    • ConcurrentMapCache:默认实现,不需要额外配置。
    • RedisCache:用于集成 Redis 缓存。
    • EhCacheCache:用于集成 EhCache 缓存。

流程

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

相关概念与重要注解

Spring提供的重要缓存注解及几个重要概念:

概念/注解 作用
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、 ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable(cacheNames="xxx") @cacheable(value="xxx") 缓存此方法的返值键通过cacheNames或value来指定
@cacheable(value="cacheName",key="#id") 缓存key为id值的缓存,名称为value的值
@CacheEvict(cacheNames ="xxx",allEntries = true,beforeInvocation=false) 清空名称为xx缓存,allEntries为true,则清空所有缓存,beforeInvocation表示在方法前还是后清空
@CachePut(cacheNames="xxx") 先清空,再加入
@EnableCaching 启用注解缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略

SpEL表达式

名字 位置 描述 示例
methodName root object 当前被调用的方法名 #root.methodName
method root object 当前被调用的方法 #root.method.name
target root object 当前被调用的目标对象 root.target
targetClass root object 当前被调用的目标对象类 #root.targetClass
args root object 当前被调用的方法的参数列表 #root.args[0]
caches root object 当前方法调用使用的存列表 (如@Cacheable(value= {“cache1”, “cache2”})),
则有两 个cache root.caches[0].name
argument name evaluation context 方法参数的名字,
可以直接 #参数名,
也可以使用#p0或#a0的形式,0代表参数的索引
#iban、#a0、#p0
result evaluation context 方法执行后的返回值
(仅当方法 执行之后的判断有效),如"unless","cache put"的表达式,
"cache evict"的表达式beforeInvocation=false)
#result

使用Redis

Spring Boot默认使用的是ConcureentMapCacheMangager==>ConcureentMapCache,

为了整合 Redis 作为缓存存储,可以配置一个 CacheManager,让 Spring 的缓存注解(如 @Cacheable@CachePut@CacheEvict)使用 Redis。

原理就是把CacheManager切换RedisCacheManager,从而使用Redis缓存。

以下是基于 RedisCacheManagerCacheManager 配置示例。

加入Redis的starter

pom.xml

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml

redis:
    host: localhost
    port: 6379
    ### 连接超时时间(记得添加单位,Duration)
    timeout: 10000ms
    # Redis默认情况下有16个分片,这里配置具体使用的分片
    database: 0
    lettuce:
        pool:
        ### 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 8
        ### 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1ms
        ### 连接池中的最大空闲连接 默认 8
        max-idle: 8
        ### 连接池中的最小空闲连接 默认 0
        min-idle: 0

自定义CacheManager

config/CacheConfig.java

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 设置 Redis 缓存的配置
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1)) // 设置缓存有效期
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json())); // 使用 JSON 序列化值

        // 使用 RedisCacheManager 并设置默认缓存配置
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(cacheConfig)
            .build();
    }

    // 可选: 自定义错误处理器
    @Bean
    public SimpleCacheErrorHandler errorHandler() {
        return new SimpleCacheErrorHandler();
    }
}

入门示例

创建SpringBoot项目

core
    lombok
    Cache
web
    Web

pom.xml

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

启用缓存

使用方法就是在启动类上加上@EnableCaching注解来开启缓存功能。

@EnableCaching
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

@Cacheable

@Cacheable:将方法运行的结果进行缓存,以后再获取相同的数据时,直接从缓存中获取,不再调用方法。使用示例如下:

/**
* @Cacheable 注解的作用是从缓存中获取数据,如果缓存中没有,则调用目标方法,并将结果存入缓存。
* 级存结果是方法的返回值,相当于key:value->id:AjaxResult
*/
@Cacheable(value = "stu",key = "#id")
@GetMapping("/getById/{id}")
public AjaxResult getById( @PathVariable int id) {
    Student student = studentService.getById(id);
    return AjaxResult.success(student);
}

@Cacheable注解的属性:

属性名 描述
cacheNames/value 指定缓存的名字,缓存使用CacheManager管理多个缓存组件Cache,
这些Cache组件就是根据这个名字进行区分的。
对缓存的真正CRUD操作在Cache中定义,
每个缓存组件Cache都有自己唯一的名字,
通过cacheNames或者value属性指定,
相当于是将缓存的键值对进行分组,
缓存的名字是一个数组,也就是说可以将一个缓存键值对分到多个组里面
key 缓存数据时的key的值,默认是使用方法参数的值,
可以使用SpEL表达式计算key的值
keyGenerator 缓存的生成策略,和key二选一,都是生成键的,keyGenerator可自定义
cacheManager 指定缓存管理器(如ConcurrentHashMap、Redis等)
cacheResolver 和cacheManager功能一样,和cacheManager二选一
condition 指定缓存的条件(满足什么条件时才缓存),
可用SpEL表达式(如id>0,表示当入参id大于0时才缓存)
unless 否定缓存,即满足unless指定的条件时,
方法的结果不进行缓存,
使用unless时可以在调用的方法获取到结果之后再进行判断
(如result==null,表示如果结果为null时不缓存)
sync 是否使用异步模式进行缓存

注意:

  • 既满足condition又满足unless条件的也不进行缓存
  • 使用异步模式进行缓存时(sync=true):unless条件将不被支持

@CachePut

说明:既调用方法,又更新缓存数据,一般用于更新操作,在更新缓存时一定要和想更新的缓存有相同的缓存名称和相同的key( 可类比同一张表的同一条数据)。示例如下:

/**
* @CachePut 注解的作用是将方法的返回值存入缓存,并覆盖原有缓存。
* 级存结果是方法的返回值,相当于key:value->id:Student
* 可以用于新增和修改
*/
@PostMapping
@CachePut(value = "stu",key = "#result['data'].id")
public AjaxResult add(@RequestBody Student stu) {
    studentService.add(stu) ;
    return AjaxResult.success(stu) ;
}

/**
* @CachePut 注解的作用是将方法的返回值存入缓存,并覆盖原有缓存。
* 级存结果是方法的返回值,相当于key:value->id:Student
* 可以用于新增和修改
*/
@PutMapping
@CachePut(value = "stu", key = "#stu.id")
public AjaxResult update(@RequestBody Student stu) {
    studentService.update(stu) ;
    return AjaxResult.success(stu) ;
}

@CacheEvict

说明:缓存清除,清除缓存时要指明缓存的名字和key,相当于告诉数据库要删除哪个表中的哪条数据,key默认为参数的值。

注意:allEntries属性是指是否清除指定缓存中的所有键值对,默认为false,设置为true时会清除缓存中 的所有键值对,与key属性二选一使用。beforeInvocation 指在@CacheEvict注解的方法调用之前清除指定缓存,默认为false,即在方法调用之后清除缓存。设置为true时则会在方法调用之前清除缓存( 在方法调用之前还是之后清除缓存的区别在于方法调用时是否会出现异常,若不出现异常,这两种设置没有区别,若出现异常,设置为在方法调用之后清除缓存将不起作用,因为方法调用失败了) 。

示例:

/**
* @CacheEvict 注解的作用是清除缓存,即stu::id对应的缓存。
*/
@DeleteMapping("/{id}")
@CacheEvict(value = "stu", key = "#id")
public AjaxResult delete(@Parameter(description = "学生ID") @PathVariable int id) {
    studentService.delById(id);
    return AjaxResult.success();
}

简单示例

@RestController
@RequestMapping("/cache")
@RequiredArgsConstructor
public class CacheController {

    private final StudentServiceI studentService;

    @Cacheable(value = "stu2",key = "#id")
    @GetMapping("/getById2/{id}")
    @Operation(summary = "根据id获取学生2")
    public AjaxResult getById2( @PathVariable int id) {
        Student student = studentService.getById(id);
        return AjaxResult.success(student);
    }

    /**
     * @Cacheable 注解的作用是从缓存中获取数据,如果缓存中没有,则调用目标方法,并将结果存入缓存。
     * 级存结果是方法的返回值,相当于key:value->id:AjaxResult
     */
    @Cacheable(value = "stu",key = "#id")
    @GetMapping("/getById/{id}")
    @Operation(summary = "根据id获取学生")
    public AjaxResult getById( @PathVariable int id) {
        Student student = studentService.getById(id);
        return AjaxResult.success(student);
    }

    /**
     * @CachePut 注解的作用是将方法的返回值存入缓存,并覆盖原有缓存。
     * 级存结果是方法的返回值,相当于key:value->id:Student
     * 可以用于新增和修改
     */
    @PostMapping
    @Operation(summary = "新增学生")
    @CachePut(value = "stu",key = "#result['data'].id")
    public AjaxResult add(@RequestBody Student stu) {
        studentService.add(stu) ;
        return AjaxResult.success(stu) ;
    }

    /**
     * @CachePut 注解的作用是将方法的返回值存入缓存,并覆盖原有缓存。
     * 级存结果是方法的返回值,相当于key:value->id:Student
     * 可以用于新增和修改
     */
    @PutMapping
    @CachePut(value = "stu", key = "#stu.id")
    @Operation(summary = "修改学生")
    public AjaxResult update(@RequestBody Student stu) {
        studentService.update(stu) ;
        return AjaxResult.success(stu) ;
    }

    /**
     * @CacheEvict 注解的作用是清除缓存,即stu::id对应的缓存。
     */
    @DeleteMapping("/{id}")
    @Operation(summary = "删除学生")
    @CacheEvict(value = "stu", key = "#id")
    public AjaxResult delete(@Parameter(description = "学生ID") @PathVariable int id) {
        studentService.delById(id);
        return AjaxResult.success();
    }
}

复杂示例

1、po/Score.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Score implements Serializable {
    private Integer id;
    private Double chinese;
    private Double math;
    private Double english;
}

2、controller/ScoreController.java

@RestController
@RequestMapping("/score")
public class ScoreController {

    @Autowired
    private ScoreService scoreService;

    @GetMapping("/list")
    public List<Score> list(){
        return scoreService.list();
    }

    @GetMapping("/byId")
    public Score byId(){
        return scoreService.byId(1);
    }

    @GetMapping("/del")
    public void del(){
        scoreService.del(1);
    }

    @GetMapping("/update")
    public void update(){
        Score score = new Score();
        score.setId(1);
        scoreService.update(score);
    }
}

3、service/ScoreServie.java

@Service
public class ScoreService {

    /*  存放缓存 
        缓存的key==>cacheNames::key
        #root.methodName 获取方法名
    */
    @Cacheable(cacheNames = "scores",key = "#root.methodName")
    public List<Score> list() {
        System.out.println("from database list");
        Score s1 = new Score(1,15.0,16.0,17.0);
        Score s2 = new Score(2,15.0,16.0,17.0);
        Score s3 = new Score(3,15.0,16.0,17.0);
        return Arrays.asList(s1,s2,s3);
    }

    /*  存放缓存 
        缓存的key==>cacheNames::key
        #id 获取参数值
    */
    @Cacheable(cacheNames = "score",key = "#id")
    public Score byId(int id) {
        System.out.println("from database byId");
        Score s1 = new Score(1,15.0,16.0,17.0);
        return  s1;
    }

    /*
        @CacheEvict 删除key为score::id缓存
        @CacheEvict(cacheNames ="score",key = "#id")
        如果需要删除多个需要使用组合缓存注解@Caching(evict={@CacheEvict(),@CacheEvict()})
    */
    @Caching(evict = {@CacheEvict(cacheNames ="score",key = "#id"),@CacheEvict(cacheNames = "scores",allEntries = true)})
    public void del(int id) {
        System.out.println("from database del");
    }

    //  @CachePut 修改key为score::id缓存,键为返回的对象,所以修改时必须要返回对象
    @CachePut(cacheNames ="score",key = "#score.id")
    @CacheEvict(cacheNames = "scores",allEntries = true)
    public Score update(Score score) {
        System.out.println("from database update");
        score = new Score(1,15.0,16.0,117.0);
        return  score;
    }
}

复杂示例 - 自定义key

@Cacheable详解

常用属性

  • cacheNames/value 指定缓存组的名称
  • key:缓存数据使用的键名,默认使用的是方法的参数的值,还可以使用SpEL编写
  • keyGenerator:key的生成器,自定义key。一般key与keyGenerator二选一
  • cacheManger:指定缓存管理器
  • condition : 满足条件缓存
  • unless: 否定缓存

StudentService.java

package net.wanho.service;

@Service
public class StudentService {

//    @Cacheable(cacheNames ="stus",key = "#root.methodName")
    @Cacheable(cacheNames = "stus",keyGenerator = "mykeyGenerator")
    public List<Student> findAllStudents(){
        System.out.println("查询数据库,获取所有学生");
        return Arrays.asList(new Student(1,"张三",18,"男","南京",new Date()),new Student(2,"张三2",28,"男","南京",new Date()));
    }

//    @Cacheable(value = "stu",key = "#root.methodName+'['+#root.args[0]+']'")
    @Cacheable(value = "stu",key = "#id",condition = "#id==1")
    public Student student(int id){
        System.out.println("student(int id)");
        if(id==1){
            return new Student(1,"张三",18,"男","南京",new Date());
        }else{
            return new Student(2,"张三2",28,"女","南京",new Date());
        }
    }

    @CacheEvict(cacheNames ="stus")
    public void clearCache(){
        System.out.println("清除缓存");
    }

    @CachePut(cacheNames ="stu",key = "#stu.id")
    public Student update(Student stu){
        System.out.println("更新数据,会自动缓存");
        return new Student(11,"张三",18,"男","南京",new Date());

    }
}

key生成器

//缓存键的生成器
@Bean("mykeyGenerator")
public KeyGenerator keyGenerator(){
    return new KeyGenerator(){
        @Override
        public Object generate(Object o, Method method, Object... objects) {
            return method.getName()+ Arrays.asList(objects).toString()+new Date().toLocaleString();
        }
    };
}
「点点赞赏,手留余香」

还没有人赞赏,快来当第一个赞赏的人吧!

admin给Admin打赏
×
予人玫瑰,手有余香
  • 2
  • 5
  • 10
  • 20
  • 50
2
支付

声明:本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

admin
Admin 关注:0    粉丝:0
这个人很懒,什么都没写

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享