Cache 缓存接口,定义缓存操作,实现由:RedisCache.EhCache.ConcurrentMapCache等
CacheManager : 缓存管理器,管理各种缓存(Cache)组件
@Cacheable : 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict : 清空缓存
@CachePut : 保证方法被调用,又希望结果被缓存
@EnableCaching : 开启基于注解的缓存
KeyGenerator : 缓存数据时key生成策略
serialize : 缓存数据 时value序列化策略
/**
* 将方法的运行结果进行缓存; 以后再要相同的数据,直接从缓存中获取,不用调用方法
*
* CacheManager:管理福讴歌Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件中有自己唯一一个的名字
* 几个属性:
* cacheNames/value: 指定缓存的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
*
* key:缓存数据时用的key; 可以用它来指定, 默认是使用方法参数的值 1-方法的返回值
* 编写SpEL ; #id;参数id的值 #a0 #p0 #root.args[0]
* key = "#root.methodName+'['+#id+']'
*
* keyGenerator: key的生成器;可以自己指定key的生成器的组件id
* key/keyGenerator : 二选一
* cacheManager: 指定缓存管理器; 或者cacheResolver 也是二选一 作用都一样
* condition: 指定符合条件的情况下才缓存;
* ,condition = "#id > 0"
* condition = "#a0>1" 第一个参数的值>1才进行缓存
* "#a0>1 and #root.methodName eq ''"
* unless:否定缓存,当unless指定的条件为true,方法的返回值就不会缓存;可以获取到结果进行判断
* ,unless = "#result == null"
* unless = "#a0==2" 如果第一个参数的值是2,结果不缓存
* sync:是否使用异步模式 异步模式下 unless就不支持了
*
* 原理:
* 1.自动配置类;CacheAutoConfiguration
* 2.缓存的配置类
* org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
* org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
* org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
* org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
* org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
* org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
* 3.哪个配置类默认生效 SimpleCacheConfiguration
* 4.给容器中注册了一个CacheManager:ConcurrentMapCacheManager
* 5.可以获取和穿件ConcurrentMapCache类型的缓存组件;它的作用键该数据保存在ConcurrentMap中;
*
* 运行流程:
* @Cacheable:
* 1.方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
* (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建
* 2.去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
* key是按照某种策略生成的; 默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
* SimpleKeyGenerator生成key的m默认策略;
* 如果没有参数; key=new SimpleKey();
* 如果有一个参数; key=参数的值
* 如果有多个参数; key=new SimpleKey(params);
* 3.没有查到缓存就调用目标方法;
* 4.将目标方法返回的结果,放进缓存中
*
* @Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有
* 就运行方法,并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
*
* 核心:
* 1).使用CacheManager[ConcurrentMapCacheManager]按照名字得到Cache[ConcurrentMapCache]组件
* 2).key使用keyGenerator生成的,默认是SimpleKeyGenerator
*
* @param id
* @return
*/
@Cacheable(cacheNames = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
其实@Cacheable重点就是先判断缓存中是否存在,如果不存在在执行方法,反之则不需要再执行方法
/**
* @CachePut : 即调用方法,又更新缓存数据; 同步更新缓存
* 修改了数据库的某个数据,同时更新缓存
* 运行时机:
* 1,先调用目标方法
* 2.将目标方法的结果缓存起来
*
* 测试步骤
* 1.查询1号员工;查到的结果会放在缓存中
* key:1 value: lastName:张三
* 2.以后查询还是之前的结果
* 3.更新一下一号员工;
* 将方法的返回值也放进缓存了;
* key: 传入的employee对象, 值: 返回的employee对象;
* 4.查询一号员工?
* 应该是更新后的员工;
* key = "#employee.id" 使用传入的参数的员工的id
* key = "#result.id" 使用返回后的id
* @Cacheable 的key是不能用#result 取出数据
* 为什么是没更新前的?[一号员工没有在缓存中更新(实际上是key不一样)]
*
* 取缓存的key和放缓存的key必须一致
*/
@CachePut(value = "emp",key = "#reusult.id")
public Employee updateEmp(Employee employee){
System.out.println("update : "+employee);
employeeMapper.updataEmp(employee);
return employee;
}
@CachePut的重点就是先执行方法,在把结果放入到缓存中,也就是说如果一个方法存在这个注解,则方法一定会被执行(如果方法出现异常,则不会把结果放入缓存中)
/**
* @CacheEvict: 缓存清除
* key: 指定要清除的数据 默认是传入的参数
* allEntries: 是不是要把emp缓存中的所有数据都删除
* ,allEntries = true 清除所有数据 默认是false
* beforeInvocation = false; 缓存的清除是否在方法之前执行
* 默认是false 代表缓存清除操作 在方法之后执行,如果出现异常缓存就不会清除,因为在方法执行之后
*
* beforeInvocation = true;
* 代表清除缓存操作是在方法执行之前执行,无论方法是否出现异常,都会清除缓存
*/
@CacheEvict(value = "emp" ,key = "#id")
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
}
@CacheEvict 清除缓存,key是指定要清除的数据,如果不给值默认就是传入的数据
要注意的是 allEntries 当这个被赋值为true时,表示要把CacheManager中的所有缓存全部清除掉,但不是所有的CacheManager中的缓存
还需要注意的是beforeInvocation 默认值为false 意思为方法执行之后执行清除缓存操作,当被赋值为true时,则代表方法执行之前执行,则无论方法出不出现异常,都会清除缓存
//@Caching 定义复杂缓存注解 CachePut导致方法一定要执行
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
@Caching 可以定义复杂缓存注解 但标注了CachePut时,方法一定会执行,不会因为@Cacheable注解中找到key所指的数据而不执行方法.
我们从RedisCacheConfiguration中可以看到这段代码
@Configuration
@ConditionalOnClass({RedisConnectionFactory.class})
@AutoConfigureAfter({RedisAutoConfiguration.class})
@ConditionalOnBean({RedisConnectionFactory.class})
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class RedisCacheConfiguration {
@ConditionalOnMissingBean({CacheManager.class})通过这个注解可以知道如果我们想要用自己配置的则需要给容器中方法CacheManager即可
然后在看SpringBoot是如何创建的CacheManager
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(resourceLoader.getClassLoader()));
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet(cacheNames));
}
return (RedisCacheManager)this.customizerInvoker.customize(builder.build());
}
这段代码就是SpringBoot 帮我们生成的CacheManager
如果我们自己配置一个我们则直接可以先将其copy过来
如下
@Bean
public RedisCacheManager cust_cacheManager(RedisConnectionFactory redisConnectionFactory) {
/*
在SpringBoot的配置文件中是通过this.determineConfiguration(resourceLoader.getClassLoader())
来获得org.springframework.data.redis.cache.RedisCacheConfiguration
这个RedisCacheConfiguration是SpringBoot帮我们配置好的,我们如果自己创建一个默认的RedisCacheConfiguration
则直接通过org.springframework.data.redis.cache.RedisCacheConfiguration中的defaultCacheConfig方法来获得默认的
注意这里的RedisCacheConfiguration,
一个是org.springframework.data.redis.cache.RedisCacheConfiguration
另一个是org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
*/
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(redisCacheConfiguration);
return builder.build();
}
进入org.springframework.data.redis.cache.RedisCacheConfiguration这个类可以看到属性有
public class RedisCacheConfiguration {
private final Duration ttl;
private final boolean cacheNullValues;
private final CacheKeyPrefix keyPrefix;
private final boolean usePrefix;
private final SerializationPair<String> keySerializationPair;
private final SerializationPair<Object> valueSerializationPair;
private final ConversionService conversionService;
很显然这个类就是用来配置序列化等一些配置的
下面这段代码就是SpringBoot给我们配置的CacheManager
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(ClassLoader classLoader) {
if (this.redisCacheConfiguration != null) {
return this.redisCacheConfiguration;
} else {
Redis redisProperties = this.cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
而config = config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
这行就是配置序列化器的
SpringBoot自动配置的序列化从上面可以看到为JdkSerializationRedisSerializer 然而缺点就是占用过多空间,跨平台差,可读信也差
点开JdkSerializationRedisSerializer可以看到
public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {
实现了RedisSerialicer接口
在idea中通过按Ctrl+H可以打开实现该接口的类,其中就有
其中GenericJackson2JsonRedisSerializer Jackson2JsonRedisSerializer这两个就是SpringBoot默认给的json序列化转换器
所以很显然,我们只要把这两个类其中一个直接设置到我们自己的CacheManger中的serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(这里))
这里演示如果把json序列化放入CacheManger
@Bean
public RedisCacheManager cust_cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
//RedisCacheManager 生成器创建
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(redisCacheConfiguration);
return builder.build();
}
上面的代码变成这个样子
当然还可以自己定义一个类,并且实现RedisSerializer
只需要把Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);这段换成自己的类的对象实例
然后把对象在通过redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));这段代码进行放入即可