缓存应用场景:
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:可以拿到一个接口的所有实现,看源码是比较好用