準備工作
可以下載案例Chapter4-4-1,進行下面改造步驟。先來回顧一下在此案例中,我們做了什么內容:
- 引入了
spring-data-jpa
和EhCache
- 定義了
User
實體,包含id
、name
、age
字段 - 使用
spring-data-jpa
實現了對User
對象的數據訪問接口UserRepository
- 使用
Cache
相關注解配置了緩存 - 單元測試,通過連續的查詢和更新數據后的查詢來驗證緩存是否生效
#開始改造
- 刪除EhCache的配置文件src/main/resources/ehcache.xml
- pom.xml中刪除EhCache的依賴,增加redis的依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
application.properties
中增加redis配置,以本地運行為例,比如:
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
我們需要做的配置到這里就已經完成了,Spring Boot會在偵測到存在Redis的依賴并且Redis的配置是可用的情況下,使用RedisCacheManager
初始化CacheManager
。
為此,我們可以單步運行我們的單元測試,可以觀察到此時CacheManager
的實例是org.springframework.data.redis.cache.RedisCacheManager
,并獲得下面的執行結果:
Hibernate: insert into user (age, name) values (?, ?)
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_ from user user0_ where user0_.name=?
第一次查詢:10
第二次查詢:10
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: update user set age=?, name=? where id=?
第三次查詢:10
可以觀察到,在第一次查詢的時候,執行了select語句;第二次查詢沒有執行select語句,說明是從緩存中獲得了結果;而第三次查詢,我們獲得了一個錯誤的結果,根據我們的測試邏輯,在查詢之前我們已經將age更新為20,但是我們從緩存中獲取到的age還是為10。
#問題思考
為什么同樣的邏輯在EhCache中沒有問題,但是到Redis中會出現這個問題呢?
在EhCache緩存時沒有問題,主要是由于EhCache是進程內的緩存框架,第一次通過select查詢出的結果被加入到EhCache緩存中,第二次查詢從EhCache取出的對象與第一次查詢對象實際上是同一個對象(可以在使用Chapter4-4-1工程中,觀察u1==u2來看看是否是同一個對象),因此我們在更新age的時候,實際已經更新了EhCache中的緩存對象。
而Redis的緩存獨立存在于我們的Spring應用之外,我們對數據庫中數據做了更新操作之后,沒有通知Redis去更新相應的內容,因此我們取到了緩存中未修改的數據,導致了數據庫與緩存中數據的不一致。
因此我們在使用緩存的時候,要注意緩存的生命周期,利用好上一篇上提到的幾個注解來做好緩存的更新、刪除
#進一步修改
針對上面的問題,我們只需要在更新age的時候,通過@CachePut
來讓數據更新操作同步到緩存中,就像下面這樣:
@CacheConfig(cacheNames = "users")
public interface UserRepository extends JpaRepository<User, Long> {
@Cacheable(key = "#p0")
User findByName(String name);
@CachePut(key = "#p0.name")
User save(User user);
}
在redis-cli中flushdb,清空一下之前的緩存內容,再執行單元測試,可以獲得下面的結果:
Hibernate: insert into user (age, name) values (?, ?)
第一次查詢:10
第二次查詢:10
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: update user set age=?, name=? where id=?
第三次查詢:20
可以看到,我們的第三次查詢獲得了正確的結果!同時,我們的第一次查詢也不是通過select查詢獲得的,因為在初始化數據的時候,調用save方法時,就已經將這條數據加入了redis緩存中,因此后續的查詢就直接從redis中獲取了。
本文內容到此為止,主要介紹了為什么要使用Redis做緩存,以及如何在Spring Boot中使用Redis做緩存,并且通過一個小問題來幫助大家理解緩存機制,在使用過程中,一定要注意緩存生命周期的控制,防止數據不一致的情況出現。
#代碼示例
本文的相關例子可以查看下面倉庫中的chapter4-4-2
目錄:
- Github:https://github.com/dyc87112/SpringBoot-Learningopen in new window
- Gitee:https://gitee.com/didispace/SpringBoot-Learning