爲了進一步簡化 Redis 的使用, Spring還提供了緩存註解,使用這些註解可以有效簡化編程過程。
緩存管理器和緩存的啓用
Spring 在使用緩存註解前,需要配置緩存管理器,緩存管理器將提供一些重要的信息,如緩存類型、超時時間等。 Spring 可以支持多種緩存的使用,因此它存在多種緩存處理器,並提供了緩存處理器的接口 CacheManager 和與之相關的類。
從圖中可以看到, Spring 可以支持多種緩存管理機制,而整合 Redis,主要就是以使用類 RedisCacheManager 爲主。 在 Spring Boot的 starter 機制中,允許我們通過配置文件生成緩存管理器。
緩存管理器配置
使用配置文件生成 Redis 緩存管理器
在項目的配置文件中加入如下配置
#配置redis緩存管理器
#緩存類型,在默認的情況下,spring會自動根據上下文檢索
spring.cache.type=redis
spring.cache.cache-names=redisCache,hashCache
配置參數解析
- spring.cache.type
spring.cache.type 配置的是緩存類型,這裏配置爲 Redis, Spring Boot 就會自動生成 RedisCacheManager 對象。 - spring.cache.cache-names
spring.cache.cache-names 配置的是緩存名稱,多個名稱可以使用逗號分隔,以便於緩存註解的引用。
最後爲了使用緩存管理器,需要在 Spring Boot 的配置文件中加入驅動緩存的註解@EnableCaching,這樣就可以驅動 Spring 緩存機制工作了。
啓用緩存機制
@SpringBootApplication
@EnableCaching
public class RedisCacheApplication {
public static void main(String[] args) {
SpringApplication.run(RedisCacheApplication.class, args);
}
}
常用的緩存註解
@Cacheable
使用了此註解的方法會將返回值放到緩存數據庫中
參數解析
- value
指定一個配置文件中的緩存策略,如果沒有指定value,則使用默認的緩存策略。 - key
給存儲的值起個名稱,在查詢時如果緩存中有名稱相同的,那麼則直接從緩存中將數據返回。
@CacheEvict
清除緩存
參數解析
- value
清除指定的緩存策略的所有對象,如果沒有指定value,那麼使用默認的緩存策略。
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的生成策略
通過觀察上圖可以發現,採用此種配置,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 從數據庫中去查詢數據,而且這便是緩存註解自調用失效的問題。
緩存註解自調用失效問題
和數據庫事務的自調用失效原理一樣,因爲 Spring 的緩存機制也是基於 Spring AOP 實現的,而在 Spring 中 AOP 是通過動態代理技術來實現的,這裏的 updateUser方法調用 findUserById 方法是類內部的自調用, 並不存在代理對象的調用 ,這樣便不會出現 AOP,也就不會使用到標註在 findUserById 上的緩存註解,更不會去獲取其緩存的值了,這是需要注意的地方。也是我們在實際的工作和學習中我們需要注意這些問題。
AOP自調用失效演示案例:傳送門【該項目的AOP子模塊】
自定義緩存管理器
在 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=6000ms
#是否啓用redis的鍵前綴
spring.cache.redis.use-key-prefix=false
可以觀察到,key的規則已經發生改變。
有時候,在重置 Redis 緩存管理器時可能存在比較多的配置, 也可以不採用 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 緩存管理器的自定義。
項目案例