Spring Boot 与缓存

422 阅读6分钟

缓存应用场景:

1. 先从缓存中读数据,如果读不到的话,就去数据库读,从数据库读完之后,先存入缓存中,从而提高效率。

2. 验证码等短期有效的信息,存入缓存而不是数据库中。

JSR 107规范:

J2EE发布了JSR 107规范,主要定义了5个核心接口:

CachingProvider: (缓存提供者)控制和管理多个CacheManager

CacheManager: (缓存管理器)控制和管理多个Cache

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

Entry: 是一个存储在Cache中的key-value对

Expiry: 每一个存储在Cache中的条目有一个定义的有效期。

Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不用的缓存技术;并支持使用JCache(JSR-107)注解简化我们的开发;

Cache接口: 缓存接口,定义缓存操作,实现有RedisCache, EhCacheCache, ConcurrentMapCache等,cache接口的实现不同,缓存技术就不同

CacheManager接口: 缓存管理器, 管理各种缓存(Cache)组件

注解@Cacheable: 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存

@Cacheable
public User getUser(Integer id);
// 查找User,拿到数据后存在缓存中,下次再查找同样的User,就可以直接从缓存中取,不会再运行getUser方法

注解@CacheEvict: 清空缓存,可以给删除User的方法上加上CacheEvict,起到的作用是,当某个用户从数据库被删除时,相应的缓存也会被删除

注解@CachePut: 保证方法被调用, 又希望结果被缓存中

@CachePut
public User updateUser(User user);
// 更新User,每次更新都会调用到updateUser方法,并把更新的数据放入缓存中
// 此注解经常用于缓存更新

注解@EnableCaching: 开启基于注解的缓存

@Cacheable
public User getUser(Integer id);
// 

keyGenerator: 缓存数据时key生成策略

serialize: 缓存数据时value序列化策略

实例:

EmplyeeMapper 接口:

Cache的使用:

Step1: 在主类Application上加@EnableCaching注解

Step2: 给Service方法加上@Cacheable注解

这样每次Controller给Service发送请求时,都会直接从缓存中拿数据

可以通过打印某个包的日志,来验证。

CacheManger管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个一个缓存组件有一个唯一的名字。Cache的几个属性如下

1. cacheNames/values:制定缓存组件的名字

👇可以设置储存在哪个缓存组件中,以下是将拿出的Employee放在emp和professor这两个缓存组件中。   

 @Cacheble(cacheName = {"emp", "professor"})

2. key:缓存数据使用的key;可以自定义。默认是始终方法参数的值,如上图中,方法参数是员工的id,此时默认使用这个id做这个缓存的key。自定义key时,可以用SpEL表达式,如下👇

 @Cacheble(cacheName = {"emp", "professor"}, key = "#root.methodName+'['+#id+']'")

3. keyGenerator: key的生成器;可以自己指定key的生成器的组件id; key/keyGenerator 二选一使用。自己编写一个方法来自定义,如下👇。在调用自己写的keyGenerator时,只需要在调用的地方指定即可:@Cacheble(cacheName = {"emp", "professor"}, keyGenerator = "myKeyGenerator").

4. cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器

5. condition:指定符合条件的情况下才缓存;(👇只有当id大于0时才缓存,其中#id能够取出方法id参数的值)

 @Cacheble(cacheName = {"emp", "professor"}, condition="#id>0")

6. unless: 否定缓存

👇当取出的结果为空时,就不缓存,其中#result能取出方法返回的结果

 @Cacheble(unless = "#result == null")

7. sync:是否使用异步模式, 如果使用异步缓存,就不支持unless参数了

Cache的原理:

1. 自动配置类: CacheAutoConfiguration

2. 给容器中倒入缓存的配置组件,共11个,截图如下

3. 默认生效的配置类:SimpleCacheConfiguration;

4. 给容器中注册了一个CacheManager: ConcurrentMapCacheManager

5. 可以获取和创建ConcurentMapCache类型的缓存组件,他的作用是将数据保存在ConcurrentMap中

运行流程(@Cacheable注解):

1. 方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取(CacheManager拿相应的缓存)。第一次获取缓存,因为不存在缓存组件,所以会先创建出缓存组件,并放在cacheMap中。

2. 去Cache中查找缓存,使用一个key,默认就是方法的参数; key就是按照某种策略声称出来的,默认调用了keyGenerator的generate方法。SimpleKeyGenrator生成key的默认策略:

    如果没有参数,key = SimpleKey();

    如果有一个参数, key = 参数的值

    如果有多个参数,key = new SimpleKey(params);

3. 没有查到缓存就调用目标方法

4. 将目标方法返回的结果,放进缓存中

@Cacheable标注的方法执行之前,先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有,就运行方法并放入缓存,以后再来调用就可以直接使用缓存中的数据。

核心:

1. 使用CacheManager按照名字得到Cache组件

2. key使用KeyGenrator生成,默认是SimpleKeyGenrator

@CachePut的使用

@CachePut是即调用方法,又更新缓存数据;修改了数据库的某个数据,同时更新缓存。

以上示例都使用的@Cacheable注解,约束了getEmployee的方法。CachePut是放在updateEmployee方法上。这两个注解有很多不同。

CachePut的运行时机:

1. 先调用目标方法

2. 将目标方法的结果缓存起来(默认更新缓存后,缓存记录的key变了,不再是参数了,所以显示指定key的值为#result.id,就可以直接更新原有的缓存的key了,具体如下👇)

@CacheEvict的使用

当删除一个员工时,同时把缓存数据中相应的的数据删掉。

属性:

allEntires, 如果@CacheEvict的这个属性被设置为true,那么名为emp的这个缓存器中的所有数据清空掉。

beforeInvocation, 默认值为false,代表在目标方法之后执行清除缓存的操作。这种情况下,当目标方法中有Error时,就不会执行到清除这个缓存的操作,再次拿相应数据时,还是可以拿到的。

@Caching 注解 

是以上三种注解的组合注解,可以一次性定义多个缓存规则。以下示例可以允许用Employee员工的id, email, lastName都能直接从缓存中获取员工。⚠️因为Caching中自定义添加了@CachePut,所以每次调用这个方法时,方法都会被执行,不论缓存中是否存有目标Employee的值。

@CacheConfig注解

可以添加在类中,可以把这个类中的共同的部分抽取到类的@CacheConfig注解中。作用:抽取某个类中缓存的共同部分。

序列化

给redis保存数据时,如果直接保存对象,则需要将对象序列化。

有两种方式:

1.自己将对象转为json

2. redisTemplate默认的序列化规则:改变默认的序列化规则

tips:

断点打在方法上,会使方法运行的变慢,尽量打在方法体内。

Control + h:可以拿到一个接口的所有实现,看源码是比较好用

Reference:

B站视频教程