2017年12月27日 星期三

Java Spring - Caching Data

  • Cache
    • 如果問題的答案不會那麼頻繁變更,那麼使用相同方式一遍遍去獲得就是一種浪費
    • 可能還會對performance產生負面影響
    • Caching可以store經常會用到的information
  • Enabling cache support
    • 可使用annotation-driven的方式,或是XML
    • 使用@EnableCaching能夠啟用Cache
    • @Configuration
      @EnableCaching // enable Cache
      public class CachingConfig {
        @Bean
        public CacheManager cacheManager() {
          return new ConcurrentMapCacheManager();
        }
      }
      
    • XML: 使用cache namespace
    • <?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>
      
    • 本質上,@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
    • @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的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: 
    • <ehcache>
        <cache name="spittleCache" maxBytesLocalHeap="50m" timeToLiveSeconds="100"> <!--最大儲存空間為50MB,存活時間100秒 -->
        </cache>
      </ehcache>
      
    • 除上述之外的其他配置
  • 可以使用Sping的CompositeCacheManager來擁有多個cache manager
    • 會透過iterate來找之前所cache的值
    • 以下呈現如何建立CompositeCacheManager bean,從 JCacheCacheManager開始尋找JCacheCacheManager, an EhCacheCacheManager, and a RedisCacheManager.
    • @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;
      }
      
    • 會從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
    • @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;
       }
      }
      
    • 當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下標註即可
    • @Cacheable("spittleCache")
      Spittle findOne(long id);
      
  • 將value放置cache中
    • 帶有@CachePut的method無條件地會被呼叫,而且return value也會被放到cache之中,這可以在request之前就被cache住
    • 假設當一個全新的Spittle透過SpittleRepository的save()保存之後,很可能馬上就會request這個record。所以當save()呼叫後,立刻將Spittle塞到cache是很有意義的。這樣當其他人透過findOne()對他query的時候,他就已經準備就緒了。為了實現這點,可以使用@CachePut
    • @CachePut("spittleCache")
      Spittle save(Spittle spittle);
      
    • 以上範例表示,呼叫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
    • @CachePut(value="spittleCache", key="#result.id")
      Spittle save(Spittle 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,則:
    • @Cacheable(value="spittleCache" unless="#result.message.contains('NoCache')") // 如果含有NoCache,則不cache
      Spittle findOne(long id);
      
    • 下例表示,若id>=10,則不會在cache找,就好像這個method沒有添加過@Cacheable一樣。因為condition在method上可能是不能使用cache,所以不能在return method之後再確定是不是要關閉cache,所以不能使用#result
    • @Cacheable(value="spittleCache"
          unless="#result.message.contains('NoCache')"
          condition="#id >= 10")
      Spittle findOne(long id);
      
  • Removing cache entries
    • @CacheEvict不會在cache上加上任何東西,相反的,還會刪除entry
    • 以下表示spittleCache已不適用,可以刪掉了
    • @CacheEvict("spittleCache")
      void remove(long spittleId);
      
    • @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
    • <?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">
      
    • cache namespaces:
      • <cache:annotation-driven> 和@EnableCaching非常相似
      • <cache:advice>
      • <cache:caching>
      • <cache:cacheable>
      • <cache:cache-put>
      • <cache:cache-evict>
      <?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>內有一個default的cache manager element是設定cacheManager,default id是cacheManager,剛好和上例橘色部分ID相同,所以不用再宣告。但如果ID不同(多個cache manager可能有此問題),則可以透過設定cache-manager指定要使用那個cache manager
    • 另外可以看到上例的 cache="spittleCache"重複出現,可以使用<cache:caching>指定cache name
    • <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>
      
    • <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

沒有留言:

張貼留言