- Cache
- 如果問題的答案不會那麼頻繁變更,那麼使用相同方式一遍遍去獲得就是一種浪費
- 可能還會對performance產生負面影響
- Caching可以store經常會用到的information
- Enabling cache support
- 可使用annotation-driven的方式,或是XML
- 使用@EnableCaching能夠啟用Cache
- XML: 使用cache namespace
- 本質上,@EnableCaching和<cache:annotation-driven />是相同的,都會創一個aspect並觸發Spring cache annotation的pointcut。
- 根據使用的annotation以及cache的狀態,這個aspect會從cache中獲得data,將data add到cache之中或者從cache中移除某個值
- 除了enable annotation-driven cache,還declare cache manager的bean,這是Spring cache abstract 的core,他能夠與多個流行的cache implementation進行integration
- ConcurrentMapCacheManager簡單,對開發測試都好用。但他的cache是 memory-based,lifecycle和application是關聯的,對大型的EE applicaiton來說可能不是個最理想的選擇。但很多其他的可以使用
- Configuring a cache manager
- Spring內建五個cache-manager implementations
- SimpleCacheManager
- NoOpCacheManager
- ConcurrentMapCacheManager
- CompositeCacheManager
- EhCacheCacheManager
- 除了core Spring framework,Spring Data又提供兩個cache managers
- RedisCacheManager (from Spring Data Redis)
- GemfireCacheManager (from Spring Data GemFire)
- 很多可以選擇,必須選一個cache manager,在Spring application context中,以bean的形式對其進行配置。
- 使用Ehcache
- Ehcache是目前受歡迎的cache manager。這個cache manager就是EhCacheCacheManager
- 因為需要EhCache的CacheManager,所以需要declare一個CacheManager的bean
- Spring提供了EhCacheManagerFactoryBean來create EhCache的CacheManager
- new EhCacheManagerFactoryBean()產生的並不是EhCacheManagerFactoryBean instance,而是CacheManager的bean,因此可以被injects到EhCacheCacheManager
- 需要使用XML定義EhCache的配置。透過ehCacheFactoryBean.setConfigLocation告知EhCache的file位置
- ehcache.xml:
- 除上述之外的其他配置
- 可以使用Sping的CompositeCacheManager來擁有多個cache manager
- 會透過iterate來找之前所cache的值
- 以下呈現如何建立CompositeCacheManager bean,從 JCacheCacheManager開始尋找JCacheCacheManager, an EhCacheCacheManager, and a RedisCacheManager.
- 會從JCacheCacheManagerd開始找JCache,然後透過EhCacheCacheManager找EhCache,最後使用RedisCacheManager找Redis,完成cache的尋找
- Annotating methods for caching
- 在Spring啟用cache時,會創建一個aspect,他觸發一個或更多的Spring cache annotation
- 以下的annotation可以放在class或method上。當放在class,cache就能夠應用到這個class的所有method上
- @Cacheable: 先在cache中找entry,如果找到了,就不會call method;如果沒找到,method會被call而且return value會放在cache中
- @CachePut: 不會在cache中找match的entry,target method總是會被call,並將return填入cache中
- 兩者共用的attributes
- value
- condition
- key
- unless
- @CacheEvict
- @Caching
- 最簡單就是使用一個value。此範例是假設findOne()後,Spittle不會再改變,則可以將Spittle cache,避免對db的不必要access
- 當call findOne(),cache aspect會攔截並在cache中找spittleCache存的return value。cache的key是傳到findOne的id。如果按照這個id能夠找到value,則會return找到的value。method不會再invoke
- 這樣的寫法只有JdbcSpittleRepository的findOne()可以使用cache,如果想要讓所有implement SpittleRepository interface的findOne()都能使用cache,那麼只要在interface的method下標註即可
- 將value放置cache中
- 帶有@CachePut的method無條件地會被呼叫,而且return value也會被放到cache之中,這可以在request之前就被cache住
- 假設當一個全新的Spittle透過SpittleRepository的save()保存之後,很可能馬上就會request這個record。所以當save()呼叫後,立刻將Spittle塞到cache是很有意義的。這樣當其他人透過findOne()對他query的時候,他就已經準備就緒了。為了實現這點,可以使用@CachePut
- 以上範例表示,呼叫save的時候,reutrn的spittle就會被放到spittleCache中
- default的key是spittle,有點奇怪。顯然在這個情境中,default key不是想要的,因此需要的cache key是新存入spittle的ID,而不是spittle本身。所以這裡需要指定一個key,而不是默認的key
- Customize cache key
- @Cacheable及@CachePut都有一個key的attribute,可以替換到原本default的key
- 這是透過SpEL expression計算得到的
- 常見的用法是所定義的expression和cache的value有關,根據此計算得到key
- SpEL中有一些可用的caching metadata可以使用
- #root.args
- #root.caches
- #root.target
- #root.targetClass
- #root.method
- #root.methodName
- #result 不能用在@Cacheable上
- #Argument
- 對於save()主要想要的key是存入的spittle的id attribute,可以使用#result得到return的Spittle
- Conditional caching
- 在某些場景下可能希望cache關閉
- @Cacheable and @CachePut提供兩個attribute(key, unless),這兩個都接受SpEL expression
- 如果unless return true,則return value不會放在cache中
- 如果condition return false,則cache會被禁用
- 相異處
- 表面上看起來unless和condition是做一樣的事情,事實上unless只能阻止將object放入cache,但是當這個call method,依然會去cache進行query,如果找到匹配的value,就會return找到的值。
- condition如果如果是false,那麼在這個method被call的過程中,cache是禁用的,不會去cache找,return也不會放在cache中
- 假設對於message attribute包含NoCache的Spittle object,若不想對其進行cache,則:
- 下例表示,若id>=10,則不會在cache找,就好像這個method沒有添加過@Cacheable一樣。因為condition在method上可能是不能使用cache,所以不能在return method之後再確定是不是要關閉cache,所以不能使用#result
- Removing cache entries
- @CacheEvict不會在cache上加上任何東西,相反的,還會刪除entry
- 以下表示spittleCache已不適用,可以刪掉了
- @CacheEvict可以用在return void的時候,@Cacheable及@CachePut不行
- @CacheEvict可以使用的attribute(沒有unless)
- value
- key
- condition
- allEntries
- beforeInvocation
- 使用XML declare cache
- 有可能要在第三方的code使用cache,或是不想使用Spring annotation
- 因為cache本身的使用就是一種aop,所以會和aop namespace結合起來使用
- XML至少要有cache和aop namespaces
- cache namespaces:
- <cache:annotation-driven> 和@EnableCaching非常相似
- <cache:advice>
- <cache:caching>
- <cache:cacheable>
- <cache:cache-put>
- <cache:cache-evict>
- <cache:advice>內有一個default的cache manager element是設定cacheManager,default id是cacheManager,剛好和上例橘色部分ID相同,所以不用再宣告。但如果ID不同(多個cache manager可能有此問題),則可以透過設定cache-manager指定要使用那個cache manager
- 另外可以看到上例的 cache="spittleCache"重複出現,可以使用<cache:caching>指定cache name
- <cache:caching>有幾個可以共享的attribute
- cache: store cache value
- condition: SpEL,若得到的值是false則禁用
- key: cache value的id
- method: cache method
- <cache:cacheable> and <cache:cache-put> 有 unless
- <cache:cache-evict>
- all-entries: 如果是true,所有cache都會被清掉。如果是false,只有符合的entry才會被清除
- before-invocation:如果是true,entry在call之前會被清除;反之則反
- 以上兩個default是true,表示如果使用<cache:cache-evict>不配置這兩個attribute時,會在method完成後只刪除要刪除的entry
@Configuration @EnableCaching // enable Cache public class CachingConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /> <bean id="cacheManager" class= "org.springframework.cache.concurrent.ConcurrentMapCacheManager" /> </beans>
@Configuration @EnableCaching public class CachingConfig { @Bean // Configure EhCacheCacheManager public EhCacheCacheManager cacheManager(CacheManager cm) { // EhCache的CacheManager被injects到Spring的 EhCacheCacheManager return new EhCacheCacheManager(cm); } @Bean // EhCacheManagerFactoryBean public EhCacheManagerFactoryBean ehcache() { EhCacheManagerFactoryBean ehCacheFactoryBean = new EhCacheManagerFactoryBean(); // 產生了bean ehCacheFactoryBean.setConfigLocation( new ClassPathResource("com/habuma/spittr/cache/ehcache.xml")); return ehCacheFactoryBean; } }
<ehcache> <cache name="spittleCache" maxBytesLocalHeap="50m" timeToLiveSeconds="100"> <!--最大儲存空間為50MB,存活時間100秒 --> </cache> </ehcache>
@Bean public CacheManager cacheManager( net.sf.ehcache.CacheManager cm,
javax.cache.CacheManager jcm) {
CompositeCacheManager cacheManager = new CompositeCacheManager(); //建立CompositeCacheManager List<CacheManager> managers = new ArrayList<CacheManager>(); managers.add(new JCacheCacheManager(jcm)); //開始新增多個manager managers.add(new EhCacheCacheManager(cm)) managers.add(new RedisCacheManager(redisTemplate()));
cacheManager.setCacheManagers(managers); return cacheManager; }
@Cacheable("spittleCache") // cache method結果 public Spittle findOne(long id) { try { return jdbcTemplate.queryForObject(SELECT_SPITTLE_BY_ID, new SpittleRowMapper(), id); } catch (EmptyResultDataAccessException e) { return null; } }
@Cacheable("spittleCache") Spittle findOne(long id);
@CachePut("spittleCache") Spittle save(Spittle spittle);
@CachePut(value="spittleCache", key="#result.id") Spittle save(Spittle spittle);
@Cacheable(value="spittleCache" unless="#result.message.contains('NoCache')") // 如果含有NoCache,則不cache Spittle findOne(long id);
@Cacheable(value="spittleCache" unless="#result.message.contains('NoCache')" condition="#id >= 10") Spittle findOne(long id);
@CacheEvict("spittleCache") void remove(long spittleId);
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <aop:config> <aop:advisor advice-ref="cacheAdvice" pointcut= "execution(* com.habuma.spittr.db.SpittleRepository.*(..))"/><!--任意method時會使用到--> </aop:config> <cache:advice id="cacheAdvice"> <cache:caching> <cache:cacheable <--!像是@Cachable--> cache="spittleCache" method="findRecent" /> <cache:cacheable cache="spittleCache" method="findOne" /> <cache:cacheable cache="spittleCache" method="findBySpitterId" /> <cache:cache-put <--!像是@Cachable--> cache="spittleCache" method="save" key="#result.id" /> <cache:cache-evict <--!像是@CacheEvict--> cache="spittleCache" method="remove" /> </cache:caching> </cache:advice> <bean id="cacheManager" class= "org.springframework.cache.concurrent.ConcurrentMapCacheManager" /> </beans>
<cache:advice id="cacheAdvice"> <cache:caching cache="spittleCache"> <cache:cacheable method="findRecent" /> <cache:cacheable method="findOne" /> <cache:cacheable method="findBySpitterId" /> <cache:cache-put method="save" key="#result.id" /> <cache:cache-evict method="remove" /> </cache:caching> </cache:advice>
沒有留言:
張貼留言