Spring的缓存管理器
在spring项目中提供了CacheManager接口来定义缓存管理器,这样各个不同的缓存就可以实现它来提供管理器的功能了,在spring-boot-starter-data-redis.jar中自动配置了RedisCacheManager(实现了CacheManager接口),可以通过设置spring.cache.cache-names属性在启动时创建其缓存,并且可以使用spring.cache.redis.*属性配置缓存默认值。例如,以下配置创建的cache1和cache2缓存的生存时间为10分钟:
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=600000
也可以自己定义RedisCacheManager来完全控制配置。如果您要定制序列化策略,这可能很有用
package edu.cuit.hippo.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; //import org.springframework.data.redis.serializer.GenericJacksonJsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; //import tools.jackson.databind.DeserializationFeature; //import tools.jackson.databind.ObjectMapper; //import tools.jackson.databind.SerializationFeature; //import tools.jackson.databind.cfg.DateTimeFeature; //import tools.jackson.databind.json.JsonMapper; import java.time.Duration; @Configuration @EnableCaching //开启缓存功能 public class RedisConfig { public static final String CACHE_KEY_PREFIX = "cuit:cache:"; @Bean public RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) { var template = new RedisTemplate<String, Object>(); template.setConnectionFactory(redisConnectionFactory); //需要设置key序列化器 template.setKeySerializer(RedisSerializer.string()); //需要设置value序列化器 template.setValueSerializer(RedisSerializer.json()); //jdk的序列化方案 template.setHashKeySerializer(RedisSerializer.string()); template.setHashValueSerializer(RedisSerializer.json()); //json的序列化方案 return template; } @Bean public CacheManager cacheManager(@Autowired RedisConnectionFactory redisConnectionFactory){ // var om = JsonMapper.builder() // .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)//关闭严格的反序列化格式:设置在反序列化时忽略在JSON字符串中存在,而在Java中不存在的属性 // .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) //关闭将日期格式化为时间戳(日期不序列化) // .enable(SerializationFeature.INDENT_OUTPUT) //格式化输出json // .build(); // //jackson3 的序列化方案 // var serializer = new GenericJacksonJsonRedisSerializer(om); return RedisCacheManager.builder( RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory)) //创建的缓存写入器在执行缓存操作(如 put、get、remove)时,会使用 Redis 的分布式锁机制来确保操作的原子性和线程安全。在高并发场景下可以防止多个进程或线程同时修改同一缓存键导致的数据不一致问题。 .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig() .prefixCacheNameWith(CACHE_KEY_PREFIX) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json())) //json的序列化方案(jackson3中默认设置已包含上述配置) .entryTtl(Duration.ofSeconds(600))) //给缓存的key添加随机时间<br> .build(); } }
@EnableCaching表示spring IoC容器启动了缓存机制。
缓存注解简介
配置了缓存管理器后,Spring就允许用注解的方式使用缓存了。使用的注解有4个
| 注解 | 描述 |
| @Cachable | 表明在进入方法之前,Spring会先从缓存服务器中查找对应key的缓存值,如果找到缓存值,那么Spring将不再调用方法,而是将缓存值直接返回给调用者;如果没有找到缓存值,那么spring就会执行方法,将最后的结果通过key保存到缓存服务器中。 |
| @CachePut | Spring会将该方法的返回值缓存到缓存服务器中。spring不会事先去缓存服务器中查找,而是直接执行方法,然后缓存。 |
| @CacheEvict | 移除缓存对应key的值 |
| @Caching | 分组注解,能够同时应用与其它缓存 |
上述注解都可以标注在类或者方法上,如果放在类上,表示对所有的方法都有效;如果放在方法上,则只对该方法有效。在大部分情况下或放置在方法上。一般而言,对于查询,我们会考虑使用@Cacheabel,对于插入和修改,考虑使用@CachePut,对于删除操作,我们会考虑使用@CacheEvict。
注解@Cacheable和@CachePut
| 属性 | 配置类型 | 描述 |
| value | String[] | 缓存的名称,可以引用多个缓存管理器 |
| condition | String | Spring表达式,true缓存,false不缓存 |
| key | String | Spring表达式,计算对应缓存的key |
| unless | String | Spring表达式,如果值为true,则不缓存结果 |
先了解一些Spring表达式和缓存注解之间的约定,通过这些约定去引用方法的参数和返回值的内容,使得其注入key所定义的Spring表达式的结果中。
| 表达式 | 描述 | 备注 |
| #root.args | 定义传递给缓存方法的参数 | 不常用 |
| #root.caches | 数组,该方法执行是对应的缓存名称 | |
| #root.target | 执行缓存的目标对象 | |
| #root.targetClass | 目标对象的类,同#root.target.class | |
| #root.method | 缓存方法 | |
| #root.methodName | 缓存方法的名称,同#root.method.name | |
| #result | 方法返回结果值,还可以使用Spring表达式进一步读取其属性 | 该表达式不能用于注解@Cacheable。因为该注解的方法可能不会被执行,这样返回值就无从谈起了 |
| #Argument | 任意方法的参数,可以通过方法本身的名称或者下标去定义 | 比如get(Long id),想读取id这个参数,可以写为#id,或者#a0、#p0 |
案例
@Service @Transactional public class DepartmentServiceImpl implements DepartmentService { @Autowired private DepartmentRepository repository; /** * 使用@CachePut表示无论如何都会执行方法,将方法返回值再保存到缓存中 * 使用在插入数据的地方,表示插入到数据库后会同步插入到Redis缓存中 * 通过value引用缓存管理器,通过key定义键 * @param department 部门对象 * @return 部门,主键回填 */ @Override @CachePut(value="redisCacheManager",key="'redis_dept_' + #result.id") public Department save(Department department) { repository.save(department); } /** * @CacheEvict删除缓存的key */ @Override @CacheEvict(value="redisCacheManager",key="'redis_dept_' + #id") public void remove(Integer id) { //repository.deleteById(id); repository.remove(id); } /** * 使用@Cacheable定义缓存策略 * 当缓存中有值,则返回缓存数据,否则访问方法得到数据 * 通过value引用缓存管理器,通过key定义键 * @param id 部门id * @return 部门 */ @Override @Transactional(propagation = Propagation.NOT_SUPPORTED) @Cacheable(value = "redisCacheManager",key = "'redis_dept_' + #id") public Department find(Integer id) { return repository.findById(id).orElse(null); } @Override @CachePut(value="redisCacheManager",key="'redis_dept_' + #department.id") public Department edit(Department department) { repository.saveAndFlush(department); } }
注解@CacheEvict主要是为了移除缓存对应的键值对,主要用于那些删除操作
| 属性 | 配置类型 | 描述 |
| value | String[] | 缓存的名称,可以引用多个缓存管理器 |
| condition | String | Spring表达式,true缓存,false不缓存 |
| key | String | Spring表达式,计算对应缓存的key |
| allEntries | boolean | true删除特定缓存的所有键值对,默认为false【慎用】 |
| beforeInvocation | boolean | 在特定方法后移除缓存,true在方法前移除,false则在方法后移除,默认false |
适用缓存的前提是高命中率,如根据各种条件动态查询的方法返回值会根据查询条件而多样化,导致其不确定性和命中率低下,对于这样的场景,适用缓存并不能有效提高性能,故这样的场景就不再适用缓存了。

浙公网安备 33010602011771号