为了进一步简化 Redis 的使用, Spring还提供了缓存注解,使用这些注解可以有效简化编程过程, 本篇就演示一下缓存注解。【建议先看一下Spring Boot直接和Spring Data Redis整合】
缓存管理器和缓存的启用
Spring 在使用缓存注解前,需要配置缓存管理器,缓存管理器将提供一些重要的信息,如缓存类型、超时时间等。 Spring 可以支持多种缓存的使用,因此它存在多种缓存处理器,并提供了缓存处理器的接口 CacheManager 和与之相关的类。
从图中可以看到, Spring 可以支持多种缓存管理机制,但是因为当前 Redis是当下的主流,所以基于实用原则, 本篇只介绍 Redis 缓存的应用,而使用 Redis,主要就是以使用类 RedisCacheManager 为主。 在 Spring Boot的 starter 机制中,允许我们通过配置文件生成缓存管理器。
缓存管理器配置
配置 Redis 缓存管理
#配置redis缓存管理器
#缓存类型,在默认的情况下,spring会自动根据上下文检索
spring.cache.type=redis
spring.cache.cache-names=redisCache,hashCache
这样我们就配置完了缓存管理器,这里的 spring.cache.type 配置的是缓存类型,这里配置为 Redis, Spring Boot 会自动生成 RedisCacheManager 对象,而 spring.cache.cache-names 则是配置缓存名称,多个名称可以使用逗号分隔,以便于缓存注解的引用 。 为了使用缓存管理器,需要在 Spring Boot 的配置文件中加入驱动缓存的注解@EnableCaching, 这样就可以驱动 Spring 缓存机制工作了。
启用缓存机制
@SpringBootApplication
@EnableCaching
public class RedisCacheApplication {
public static void main(String[] args) {
SpringApplication.run(RedisCacheApplication.class, args);
}
}
这样就能够驱动缓存机制了 。然后需要搭建关于 SpringDataJpa 的整合来测试缓存机制的使用,关于缓存注解的应用将在测试案例中阐述。
实现类使用 Spring 缓存注解
@Override
@Cacheable(value = "redisCache", key = "'redis_user_'+#userId")
public User findUserById(Long userId) {
log.info("findUserById入参=" + userId);
Optional<User> byId = this.userDao.findById(userId);
return byId.get();
}
可以发现,采用默认的配置,key的规则是#{cacheName}::#{key}。
更新数据库中的数据
@Override
@Transactional
@CachePut(value = "redisCache", key = "'redis_user_'+#result.userId"
, condition = "#result!='null'")
public User updateUser(User user) {
log.info("updateUser入参=" + JSON.toJSONString(user));
User userById = this.findUserById(user.getUserId());
if (Objects.isNull(userById)){
System.out.println("数据错误");
}
User save = this.userDao.save(user);
return save;
}
在调用这个方法的时候,可以看到执行了查询数据库的操作,所以并没有查缓存!
更新数据,所以需要慎重一些。 一般我们不要轻易地相信缓存,因为缓存存在脏读的可能性,这是需要注意的,在需要更新数据时我们往往考虑先从数据库查询出最新数据,而后再进行操作。因此,这里使用了 findUserById 方法,这里会存在一个误区,很多人认为 findUserById 方法因为存在了注解@Cacheable,所以会从缓存中读取数据,从而拿着缓存中的读取去更新数据库的数据,这是一个比较危险的行为,因为缓存的数据可能存在脏数据, 然后这里的事实是这个注解@Cacheable 失效了!也就是说使用 updateUser 方法调用 findUserById 方法的逻辑,并不存在读取缓存的可能,它每次都会执行 SQL 查询数据。关于这个缓存注解失效的问题,在后续再给予说明,这里只是提醒阅读的朋友,更新数据时应该谨慎一些,尽量避免读取缓存数 据,因为缓存会存在脏数据的可能。
缓存注解自调用失效问题
在上边的代码中,曾经说明过在findUserById方法上的注解将会失效,为什么会这样呢?其实在数据库事务中也存在这种问题, 只要回顾一下就清楚了,那是因为 Spring 的缓存机制也是基于 Spring AOP 的原理,而在 Spring 中 AOP 是通过动态代理技术来实现的,这里的 updateUser方法调用 findUserById 方法是类内部的自调用, 并不存在代理对象的调用 ,这样便不会出现 AOP,也就不会使用到标注在 findUserById 上的缓存注解去获取缓存的值了,这是需要注意的地方。也是我们在实际的工作和学习中我们需要注意这些问题。
自定义缓存管理器
在 Spring Boot 中,如果采取如上配置,则 RedisCacheManage中会采用永不超时的机制,如果我们并不希望采用 Spring Boot 机制带来的键命名方式,也不希望缓存永不超时,这时我们可以自定义缓存管理器。毕竟永不超时的机制不利于数据的及时更新。
重置 Redis 缓存管理器
#是否允许redis缓存空值
spring.cache.redis.cache-null-values=true
#redis的键前缀
spring.cache.redis.key-prefix=
#缓存超时时间戳,配置为0则不设置超时时间
spring.cache.redis.time-to-live=600ms
#是否启用redis的键前缀
spring.cache.redis.use-key-prefix=false
可以观察到,key的规则已经发生改变。
有时候,在自定义时可能存在比较多的配置, 也可以不采用 Spring Boot 自动配置的缓存管理器, 而是使用自定义的缓存管理器,这也是没有问题的。首先需要删除关于 Redis 缓存管理器的配置。然后给 IoC 容器增加缓存管理器。
自定义缓存管理器
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 自定义Redis缓存管理器
*
* @return
*/
@Bean(name = "redisCacheManager")
public RedisCacheManager iniRedisCacheManager() {
// redis加锁的写入器
RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);
// 启用redis缓存的默认配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置JDK序列化器
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
// 禁用前缀
config = config.disableKeyPrefix();
// 设置超时时间10分钟
config.entryTtl(Duration.ofMinutes(10));
RedisCacheManager redisCacheManager = new RedisCacheManager(writer, config);
return redisCacheManager;
}
这里首先注入了 RedisConnectionFactory 对象,该对象是由 Spring Boot 自动生成的。在创建 Redis 缓存管理器对象 RedisCacheManager 的时候,首先创建了带锁的 RedisCacheWriter 对象, 然后使用 RedisCacheConfiguration 对其属性进行配置,这里设置了禁用前缀,并且超时时间为 10 min:最后就通过 RedisCacheWriter 对象和 RedisCacheConfiguration 对象去构建 RedisCacheManager 对象了,这样 就完成了 Redis 缓存管理器的自定义。
项目案例点这里