-SpringBoot-Cache(EhCache)
SpringBoot提供資料快取的功能,相信非常多人已經用過cache了。因為資料庫的IO瓶頸應該大家也吃過不少虧了,所以一般情況下我們都會引入非常多的快取策略,例如引入redis,引入hibernate的二級快取等等。
SpringBoot在annotation的層面給我們實現了cache,當然這也是得益於Spring的AOP。所有的快取配置只是在annotation層面配置,完全沒有侵入到我們的程式碼當中,就像我們的宣告式事務一樣。
Spring定義了CacheManager和Cache介面統一不同的快取技術。其中CacheManager是Spring提供的各種快取技術的抽象介面。而Cache介面包含快取的各種操作,當然我們一般情況下不會直接操作Cache介面。
Spring針對不同的快取技術,需要實現不同的cacheManager,Spring定義瞭如下的cacheManger實現
CacheManger | 描述 |
SimpleCacheManager | 使用簡單的Collection來儲存快取,主要用於測試 |
ConcurrentMapCacheManager | 使用ConcurrentMap作為快取技術(預設) |
NoOpCacheManager | 測試用 |
EhCacheCacheManager | 使用EhCache作為快取技術,以前在hibernate的時候經常用 |
GuavaCacheManager | 使用google guava的GuavaCache作為快取技術 |
HazelcastCacheManager | 使用Hazelcast作為快取技術 |
JCacheCacheManager | 使用JCache標準的實現作為快取技術,如Apache Commons JCS |
RedisCacheManager | 使用Redis作為快取技術 |
當然常規的SpringBoot已經為我們自動配置了EhCache、Collection、Guava、ConcurrentMap等快取,預設使用SimpleCacheConfiguration,即使用ConcurrentMapCacheManager。SpringBoot的application.properties配置檔案,使用spring.cache字首的屬性進行配置。
spring.cache.type=#快取的技術型別 spring.cache.cache-names=應用程式啟動建立快取的名稱 spring.cache.ehcache.config=ehcache的配置檔案位置 spring.cache.infinispan.config=infinispan的配置檔案位置 spring.cache.jcache.config=jcache配置檔案位置 spring.cache.jcache.provider=當多個jcache實現類時,指定選擇jcache的實現類
在SpringBoot環境下我們需要匯入相關快取技術的依賴,並在配置類當中配置@EnableCaching開啟快取技術。
我們這裡不適用預設的ConcurrentMapCache 而是使用 EhCache
所以我在resources目錄下建立了ehcache.xml的配置檔案,然後在application.properties 設定type為ehcache(intellij有明確的提示):
ehcache.xml:
<ehcache> <!-- 指定一個檔案目錄,當EHCache把資料寫到硬碟上時,將把資料寫到這個檔案目錄下 --> <diskStore path="java.io.tmpdir"/> <!-- 設定快取的預設資料過期策略 --> <cache name="weibo" maxElementsInMemory="10000" /> <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="10" timeToLiveSeconds="120" diskPersistent="false" memoryStoreEvictionPolicy="LRU" diskExpiryThreadIntervalSeconds="120"/> <!-- maxElementsInMemory 記憶體中最大快取物件數,看著自己的heap大小來搞 --> <!-- eternal:true表示物件永不過期,此時會忽略timeToIdleSeconds和timeToLiveSeconds屬性,預設為false --> <!-- maxElementsOnDisk:硬碟中最大快取物件數,若是0表示無窮大 --> <!-- overflowToDisk:true表示當記憶體快取的物件數目達到了maxElementsInMemory界限後, 會把溢位的物件寫到硬碟快取中。注意:如果快取的物件要寫入到硬碟中的話,則該物件必須實現了Serializable接口才行。--> <!-- diskSpoolBufferSizeMB:磁碟快取區大小,預設為30MB。每個Cache都應該有自己的一個快取區。--> <!-- diskPersistent:是否快取虛擬機器重啟期資料 --> <!-- diskExpiryThreadIntervalSeconds:磁碟失效執行緒執行時間間隔,預設為120秒 --> <!-- timeToIdleSeconds: 設定允許物件處於空閒狀態的最長時間,以秒為單位。當物件自從最近一次被訪問後, 如果處於空閒狀態的時間超過了timeToIdleSeconds屬性值,這個物件就會過期, EHCache將把它從快取中清空。只有當eternal屬性為false,該屬性才有效。如果該屬性值為0, 則表示物件可以無限期地處於空閒狀態 --> <!-- timeToLiveSeconds:設定物件允許存在於快取中的最長時間,以秒為單位。當物件自從被存放到快取中後, 如果處於快取中的時間超過了 timeToLiveSeconds屬性值,這個物件就會過期, EHCache將把它從快取中清除。只有當eternal屬性為false,該屬性才有效。如果該屬性值為0, 則表示物件可以無限期地存在於快取中。timeToLiveSeconds必須大於timeToIdleSeconds屬性,才有意義 --> <!-- memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時, Ehcache將會根據指定的策略去清理記憶體。可選策略有:LRU(最近最少使用,預設策略)、 FIFO(先進先出)、LFU(最少訪問次數)。--> </ehcache>
application.properties:
spring.cache.type=ehcache spring.cache.ehcache.config=ehcache.xml在配置類配置@EnableCaching
@SpringBootApplication @EnableCaching public class DemoApplication extends WebMvcConfigurerAdapter {
然後說說4個annotation的配置:
@Cacheable 在方法執行前Spring先是否有快取資料,如果有直接返回。如果沒有資料,呼叫方法並將方法返回值存放在快取當中。
@CachePut 無論怎樣,都將方法的範湖值放到快取當中。
@CacheEvict 將一條或者多條資料從快取中刪除。
@Caching 可以通過@Caching註解組合多個註解集合在一個方法上
使用演示JPA時候的方法進行快取測試:
@Transactional @CachePut(value = "weibo",key="#weibo.weiboId") public Weibo saveWeibo(Weibo weibo){ this.weiboRepository.save(weibo); return weibo; } @Cacheable(value = "weibo") public Weibo getWeiboById(long id){ return this.weiboRepository.getByWeiboId(id); } @Transactional @CacheEvict(value = "weibo",key = "#weibo.weiboId") public void remove(Weibo weibo){ this.weiboRepository.delete(weibo); }
當然如果我們想單獨配置一下weibo這個快取可以通過ehcache.xml進行單獨配置,不過需要提醒的是 maxElementsInMemory屬性配置是必須的,否則無法啟動SpringBoot應用。
<cache name="weibo" maxElementsInMemory="10000" overflowToDisk="false" timeToIdleSeconds="60" timeToLiveSeconds="120" />
通過Controller進行測試,快取生效:
@RestController @RequestMapping("/cache") public class CacheTestController { @Autowired private WeiboService weiboService; @Autowired private UserRepository userRepository; @RequestMapping("/getWeibo/{id}") public Weibo getWeibo(@PathVariable("id") long id){ return weiboService.getWeiboById(id); } @RequestMapping("/addWeibo") public Weibo addWeibo(String username,String weiboText){ User user = userRepository.getByUsernameIs(username); Weibo weibo = new Weibo(user,weiboText,new Date(System.currentTimeMillis())); return this.weiboService.saveWeibo(weibo); } @RequestMapping("/delete/{id}") public Weibo delete(@PathVariable("id") long id){ Weibo weibo = this.weiboService.getWeiboById(id); this.weiboService.remove(weibo); return weibo; } }