Spring定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术。并支持使用JCache(JSR-107)注解简化开发。
@EnableCaching | 开启springboot的缓存功能标注在spring启动类上 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedisCache,EhCacheCache,ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 ,例标注在删除方法上 |
@CachePut | 保证方法总是被调用,且结果被缓存 |
@EnableCaching | 开启基于注解的缓存 |
KeyGenerator | 缓存数据Key的生成策略 |
serialize | 缓存数据时value序列化策略 |
@Cacheable/@CachePut/@CacheEvict 主要的属性
属性值中spel表达式可使用的对象
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
springboot的缓存的自动配置类
默认使用的SimpleCacheConfiguration给容器中注册了一个CacheManager:ConcurrentMapCacheManager
附上SimpleCacheConfiguration.java源码
ConcurrentMapCacheManager可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有查到就运行方法并将结果放入缓存;如果查到直接使用缓存中的数据且方法不被执行;
1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
生成key的默认策略;
3、没有查到缓存就调用目标方法;
4、将目标方法返回的结果,放进缓存中
核心
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
标注的方法一定会被执行方法,且更新缓存数据;同步更新缓存
运行时机:
应注意更新方法中的value值(相同的cache组件)要和查询对应,且key的值要与查询中的key一致;在@Cacheput可以使用key = "#result.id"而@Cacheable中不能使用result对象
缓存清除
key:指定要清除的数据(当key为字符串时一定要加单引号)
allEntries = true:指定清除这个缓存中所有的数据
beforeInvocation = false:缓存的清除是否在方法之前执行;默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
beforeInvocation = true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
定义复杂的缓存规则
给出一个例子
@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);
}
抽取缓存的公共配置,标注后类下方法都是有相应属性
@CacheConfig(cacheNames=“emp”,cacheManager = “employeeCacheManager”)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: 192.168.1.5
Redis常见的五大数据类型:
String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
对应的操作方法为:
redisTemplate存储对象时默认使用的序列化类为JdkSerializationRedisSerializer(jdk的序列化)我们可以切换组件使用的序列化规则
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
return template;
}
}
注:不要忘记在注入redisTemplat时使用设置后的empRedisTemplate对象
原理
CacheManager创建Cache缓存组件,组件来给缓存中存取数据
1)、引入redis的starter,容器中保存的是 RedisCacheManager;
2)、RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据的
3)、默认保存数据 k-v 都是Object;利用序列化保存;如何保存为json?
//缓存对象时不同的类应注意读取缓存数据的反序列化出错问题
@Bean
public RedisCacheManager empCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Employee> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Employee.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))//缓存过期时间为1天
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
return cacheManager;
}
@Primary//配置多个cacheManager时需指定默认使用的cacheManager这里偷懒直接任意指定一个
@Bean
public RedisCacheManager deptCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Department> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Department.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))//缓存过期时间为1天
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
return cacheManager;
}
使用cacheManager
@CacheConfig(cacheNames="emp",cacheManager = "empCacheManager") //抽取缓存的公共配置
@Service
public class EmployeeService {