Rubin's Blog

  • 首页
  • 关于作者
  • 隐私政策
享受恬静与美好~~~
分享生活的点点滴滴~~~
  1. 首页
  2. SpringBoot
  3. 正文

SpringBoot之缓存

2021年 9月 2日 879点热度 0人点赞 0条评论

前言

我们平时在工作中,缓存肯定都用过。在SpringBoot中使用缓存也很简单,下面我们来讲解一下使用步骤以及注意事项。

SpringBoot使用缓存

前置知识

JSR107

首先,我们来了解一下JSR107规范。JSR是Java Specification Requests 的缩写 ,Java规范请求,故名思议提交Java规范, JSR-107呢,就是关于如何使用缓存的规范,是java提供的一个接口规范,类似于JDBC规范,没有具体的实
现,具体的实现就是reids等这些缓存。

JSR107核心接口

Java Caching(JSR-107)定义了5个核心接口,分别是CachingProvider、CacheManager、Cache、Entry和Expiry。

  • CachingProvider(缓存提供者):创建、配置、获取、管理和控制多个CacheManager。
  • CacheManager(缓存管理器):创建、配置、获取、管理和控制多个唯一命名的Cache,Cache存在于CacheManager的上下文中。一个CacheManager仅对应一个CachingProvider。
  • Cache(缓存):是由CacheManager管理的,CacheManager管理Cache的生命周期,Cache存在于CacheManager的上下文中,是一个类似map的数据结构,并临时存储以key为索引的值。一个Cache仅被一个CacheManager所拥有。
  • Entry(缓存键值对):是一个存储在Cache中的key-value对。
  • Expiry(缓存时效):每一个存储在Cache中的条目都有一个定义的有效期。一旦超过这个时间,条目就自动过期,过期后,条目将不可以访问、更新和删除操作。缓存有效期可以通过ExpiryPolicy设置。

JSR107图示

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

使用JSR-107需导入的依赖:

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

缓存细节以及使用步骤

案例实践之前,先介绍下Spring提供的重要缓存注解及几个重要概念:

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

使用方法就是在启动类上加上@EnableCaching注解来开启缓存功能。之后就可以在方法上使用上述注解进行缓存的使用下面,我们针对几个重要注解做一下讲解。

@Cacheable

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

@Cacheable(cacheNames = {"emp"})
public Employee getEmpById(Integer id){
    Employee emp = employeeMapper.getEmpById(id);
    return emp;
}

@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条件将不被支持

可用的SpEL表达式见下表:

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

@CachePut

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

@CachePut(value = "emp",key = "#employee.id")
public Employee updateEmp(Employee employee){
    employeeMapper.updateEmp(employee);
    return employee;
}

@CacheEvict

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

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

示例:

@CacheEvict(value = "emp",key = "#id",beforeInvocation = true)
public void delEmp(Integer id){
    employeeMapper.deleteEmpById(id);
}

@CacheConfig

作用:标注在类上,抽取缓存相关注解的公共配置,可抽取的公共配置有缓存名字、主键生成器等(如注解中的属性所示):

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
  String[] cacheNames() default {};
  String keyGenerator() default "";
  String cacheManager() default "";
  String cacheResolver() default "";
}

示例:通过@CacheConfig的cacheNames 属性指定缓存的名字之后,该类中的其他缓存注解就不必再写value或者cacheName了,会使用该名字作为value或cacheName的值,当然也遵循就近原则:

@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {

  @Autowired
  EmployeeMapper employeeMapper;

  @Cacheable
  public Employee getEmpById(Integer id) {
    Employee emp = employeeMapper.getEmpById(id);
    return emp;
   }

  @CachePut(key = "#employee.id")
  
  public Employee updateEmp(Employee employee) {
    employeeMapper.updateEmp(employee);
    return employee;
   }

    @CacheEvict(key = "#id", beforeInvocation = true)
  
  public void delEmp(Integer id) {
  
    employeeMapper.deleteEmpById(id);
   }
}

基于Redis的缓存实现

SpringBoot默认开启的缓存管理器是ConcurrentMapCacheManager,创建缓存组件是ConcurrentMapCache,将缓存数据保存在一个个的ConcurrentHashMap中。开发时我们可以使用缓存中间件:redis、memcache、ehcache等,这些缓存中间件的启用很简单——只要向容器中加入相关的bean就会启用,可以启用多个缓存中间件。

引入Redis的starter:

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

配置redis:只需要配置redis的主机地址(端口默认即为6379,因此可以不指定):

spring.redis.host=127.0.0.1

配制好之后,项目会自动切换成使用Redis来缓存数据。但是这种方式的问题就是,我们的value序列化器默认使用的是JDK的序列化器,基本上没有可读性。所以我们最好使用JSON的序列化器来管理我们的缓存序列化,定制序列化器的操作如下:

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory
 redisConnectionFactory) {
    // 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
    RedisSerializer<String> strSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jacksonSeial = 
new Jackson2JsonRedisSerializer(Object.class);
    // 解决查询缓存转换异常的问题
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jacksonSeial.setObjectMapper(om);
    // 定制缓存数据序列化方式及时效
    RedisCacheConfiguration config =
  RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSeial))
.disableCachingNullValues();
    RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
    return cacheManager;
}

上述代码中,在RedisConfig配置类中使用@Bean注解注入了一个默认名称为方法名的cacheManager组件。在定义的Bean组件中,通过RedisCacheConfiguration对缓存数据的key和value分别进行了序列化方式的定制,其中缓存数据的key定制为StringRedisSerializer(即String格式),而value定制为了Jackson2JsonRedisSerializer(即JSON格式),同时还使用entryTtl(Duration.ofDays(1))方法将缓存数据有效期设置为1天。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: SpringBoot
最后更新:2022年 6月 9日

RubinChu

一个快乐的小逗比~~~

打赏 点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
文章目录
  • 前言
  • SpringBoot使用缓存
    • 前置知识
      • JSR107
      • JSR107核心接口
      • JSR107图示
    • 缓存细节以及使用步骤
      • @Cacheable
      • @CachePut
      • @CacheEvict
      • @CacheConfig
    • 基于Redis的缓存实现
最新 热点 随机
最新 热点 随机
问题记录之Chrome设置屏蔽Https禁止调用Http行为 问题记录之Mac设置软链接 问题记录之JDK8连接MySQL数据库失败 面试系列之自我介绍 面试总结 算法思维
java并发编程之JMM内存模型 SpringBoot解决中文乱码问题 Nginx详解 Neo4j之CQL MongoDB之命令解析 数据结构之线性表

COPYRIGHT © 2021 rubinchu.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

京ICP备19039146号-1