準(zhǔn)備工作
可以下載案例Chapter4-4-1,進(jìn)行下面改造步驟。先來(lái)回顧一下在此案例中,我們做了什么內(nèi)容:
- 引入了
spring-data-jpa
和EhCache
- 定義了
User
實(shí)體,包含id
、name
、age
字段 - 使用
spring-data-jpa
實(shí)現(xiàn)了對(duì)User
對(duì)象的數(shù)據(jù)訪問(wèn)接口UserRepository
- 使用
Cache
相關(guān)注解配置了緩存 - 單元測(cè)試,通過(guò)連續(xù)的查詢和更新數(shù)據(jù)后的查詢來(lái)驗(yàn)證緩存是否生效
#開始改造
- 刪除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配置,以本地運(yùn)行為例,比如:
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
我們需要做的配置到這里就已經(jīng)完成了,Spring Boot會(huì)在偵測(cè)到存在Redis的依賴并且Redis的配置是可用的情況下,使用RedisCacheManager
初始化CacheManager
。
為此,我們可以單步運(yùn)行我們的單元測(cè)試,可以觀察到此時(shí)CacheManager
的實(shí)例是org.springframework.data.redis.cache.RedisCacheManager
,并獲得下面的執(zhí)行結(jié)果:
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
可以觀察到,在第一次查詢的時(shí)候,執(zhí)行了select語(yǔ)句;第二次查詢沒有執(zhí)行select語(yǔ)句,說(shuō)明是從緩存中獲得了結(jié)果;而第三次查詢,我們獲得了一個(gè)錯(cuò)誤的結(jié)果,根據(jù)我們的測(cè)試邏輯,在查詢之前我們已經(jīng)將age更新為20,但是我們從緩存中獲取到的age還是為10。
#問(wèn)題思考
為什么同樣的邏輯在EhCache中沒有問(wèn)題,但是到Redis中會(huì)出現(xiàn)這個(gè)問(wèn)題呢?
在EhCache緩存時(shí)沒有問(wèn)題,主要是由于EhCache是進(jìn)程內(nèi)的緩存框架,第一次通過(guò)select查詢出的結(jié)果被加入到EhCache緩存中,第二次查詢從EhCache取出的對(duì)象與第一次查詢對(duì)象實(shí)際上是同一個(gè)對(duì)象(可以在使用Chapter4-4-1工程中,觀察u1==u2來(lái)看看是否是同一個(gè)對(duì)象),因此我們?cè)诟耡ge的時(shí)候,實(shí)際已經(jīng)更新了EhCache中的緩存對(duì)象。
而Redis的緩存獨(dú)立存在于我們的Spring應(yīng)用之外,我們對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)做了更新操作之后,沒有通知Redis去更新相應(yīng)的內(nèi)容,因此我們?nèi)〉搅司彺嬷形葱薷牡臄?shù)據(jù),導(dǎo)致了數(shù)據(jù)庫(kù)與緩存中數(shù)據(jù)的不一致。
因此我們?cè)谑褂镁彺娴臅r(shí)候,要注意緩存的生命周期,利用好上一篇上提到的幾個(gè)注解來(lái)做好緩存的更新、刪除
#進(jìn)一步修改
針對(duì)上面的問(wèn)題,我們只需要在更新age的時(shí)候,通過(guò)@CachePut
來(lái)讓數(shù)據(jù)更新操作同步到緩存中,就像下面這樣:
@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,清空一下之前的緩存內(nèi)容,再執(zhí)行單元測(cè)試,可以獲得下面的結(jié)果:
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
可以看到,我們的第三次查詢獲得了正確的結(jié)果!同時(shí),我們的第一次查詢也不是通過(guò)select查詢獲得的,因?yàn)樵诔跏蓟瘮?shù)據(jù)的時(shí)候,調(diào)用save方法時(shí),就已經(jīng)將這條數(shù)據(jù)加入了redis緩存中,因此后續(xù)的查詢就直接從redis中獲取了。
本文內(nèi)容到此為止,主要介紹了為什么要使用Redis做緩存,以及如何在Spring Boot中使用Redis做緩存,并且通過(guò)一個(gè)小問(wèn)題來(lái)幫助大家理解緩存機(jī)制,在使用過(guò)程中,一定要注意緩存生命周期的控制,防止數(shù)據(jù)不一致的情況出現(xiàn)。
#代碼示例
本文的相關(guān)例子可以查看下面?zhèn)}庫(kù)中的chapter4-4-2
目錄:
- Github:https://github.com/dyc87112/SpringBoot-Learningopen in new window
- Gitee:https://gitee.com/didispace/SpringBoot-Learning