1. 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图
image.png一个应用里面可以有多个缓存提供者(CachingProvider),一个缓存提供者可以获取到多个缓存管
理器(CacheManager),一个缓存管理器管理着不同的缓存(Cache),缓存中是一个个的缓存键值
对(Entry),每个entry都有一个有效期(Expiry)。缓存管理器和缓存之间的关系有点类似于数据库中
连接池和连接的关系
依赖
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
2. Spring的缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用Java
Caching(JSR-107)注解简化我们进行缓存开发。
Spring Cache 只负责维护抽象层,具体的实现由自己的技术选型来决定。将缓存处理和缓存技术
解除耦合。
每次调用需要缓存功能的方法时,Spring会检查指定参数的指定的目标方法是否已经被调用过,如
果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次
调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点:
① 确定那些方法需要被缓存
② 缓存策略
重要接口
Cache:缓存抽象的规范接口,缓存实现有:RedisCache、EhCache、
ConcurrentMapCache等
CacheManager:缓存管理器,管理Cache的生命周期
3. 重要概念&缓存注解
image.png① @Cacheable标注在方法上,表示该方法的结果需要被缓存起来,缓存的键由keyGenerator的
策略决定,缓存的值的形式则由serialize序列化策略决定(序列化还是json格式);标注上该注解之
后,在缓存时效内再次调用该方法时将不会调用方法本身而是直接从缓存获取结果
② @CachePut也标注在方法上,和@Cacheable相似也会将方法的返回值缓存起来,不同的是标
注@CachePut的方法每次都会被调用,而且每次都会将结果缓存起来,适用于对象的更新
@Cacheable
① 开启基于注解的缓存功能:主启动类标注@EnableCaching
@SpringBootApplication
@MapperScan(basePackages = "com.test.cache.mappers")
@EnableCaching //开启基于注解的缓存
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
② 标注缓存相关注解:@Cacheable、CacheEvict、CachePut
@Cacheable:将方法运行的结果进行缓存,以后再获取相同的数据时,直接从缓存中获取,不再
调用方法
@Cacheable(cacheNames = {"user"})
public User getUserById(Integer id){
User user = userMapper.getUserById(id);
return user;
}
@Cacheable注解的属性:
image.png备注:
①既满足condition又满足unless条件的也不进行缓存
②使用异步模式进行缓存时(sync=true):unless条件将不被支持
可用的SpEL表达式见下表:
image.png
@Cacheable运行流程:
①方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取(CacheManager
先获取相应的缓存,第一次获取缓存如果没有Cache组件会自动创建)
②去Cache中查找缓存的内容,使用的key默认就是方法的参数:
key默认是使用keyGenerator生成的,默认使用的是SimpleKeyGenerator
SimpleKeyGenerator生成key的默认策略:
如果没有参数:key = new SimpleKey();
如果有一个参数:key = 参数的值
如果有多个参数:key = new SimpleKey(params);
③没有查到缓存就调用目标方法
④将目标方法返回的结果放进缓存中
总结 :@Cacheable标注的方法在执行之前会先检查缓存中有没有这个数据,默认按照参数的值
为key查询缓存,如果没有就运行方法并将结果放入缓存,以后再来调用时直接使用缓存中的数
据
重点
1、使用CacheManager(ConcurrentMapCacheManager)按照名字得到
Cache(ConcurrentMapCache)组件
2、key使用keyGenerator生成,默认使用SimpleKeyGenerator
4. @CachePut&@CacheEvict&@CacheConfig
@CachePut
1、说明:既调用方法,又更新缓存数据,一般用于更新操作,在更新缓存时一定要和想更新的缓
存有相同的缓存名称和相同的key(可类比同一张表的同一条数据) 2、运行时机:
①先调用目标方法
②将目标方法的结果缓存起来
@CachePut(value = "user",key = "#user.id")
public User getUserById(Integer id){
User user = userMapper.getUserById(id);
return user;
}
总结 :@CachePut标注的方法总会被调用,且调用之后才将结果放入缓存,因此可以使用#result
获取到方法的返回值。
@CacheEvict
1、说明:缓存清除,清除缓存时要指明缓存的名字和key,相当于告诉数据库要删除哪个表中的
哪条数据,key默认为参数的值
2、属性:
value/cacheNames:缓存的名字
key:缓存的键
allEntries:是否清除指定缓存中的所有键值对,默认为false,设置为true时会清除缓存中
的所有键值对,与key属性二选一使用
beforeInvocation:在@CacheEvict注解的方法调用之前清除指定缓存,默认为false,即
在方法调用之后清除缓存,设置为true时则会在方法调用之前清除缓存(在方法调用之前还是之后
清除缓存的区别在于方法调用时是否会出现异常,若不出现异常,这两种设置没有区别,若出现异
常,设置为在方法调用之后清除缓存将不起作用,因为方法调用失败了)
@CacheEvict(value = "user",key = "#id",beforeInvocation = true)
public User deleteUser(Integer id){
userMapper.deleteById(id);
}
@CacheConfig
1、作用:标注在类上,抽取缓存相关注解的公共配置,可抽取的公共配置有缓存名字、主键生成
器等(如注解中的属性所示)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
2、示例:通过@CacheConfig的cacheNames 属性指定缓存的名字之后,该类中的其他缓存注
解就不必再写value或者cacheName了,会使用该名字作为value或cacheName的值,当然也遵循
就近原则
5. 整合Redis
1.添加Redis的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
引入redis的starter之后,会在容器中加入redis相关的一些bean,其中有两个跟操作redis相关
的:
RedisTemplate和StringRedisTemplate(用来操作字符串:key和value都是字符串),template中
封装了操作各种数据类型的操作(stringRredisTemplate.opsForValue()、
stringRredisTemplate.opsForList()等)
2.配置redis:只需要配置redis的主机地址(端口默认即为6379,因此可以不指定)
spring.redis.host=127.0.0.1
使用redis存储对象时,该对象必须可序列化(实现Serializable接口),否则会报错,此时存储的结
果在redis的管理工具中查看如下:由于序列化的原因值和键都变为了另外一种形式
SpringBoot默认采用的是JDK的对象序列化方式,我们可以切换为使用JSON格式进行对象的序列
化操作,这时需要我们自定义序列化规则(当然我们也可以使用Json工具先将对象转化为Json格式
之后再保存至redis,这样就无需自定义序列化)
网友评论