목차

Cache Abstraction

개요

Spring 3.1부터 Cache Service는 Cache 추상화(CacheManager Interface)와 Cache 추상화를 Java메소드에 제공할 수 있는 @Cacheable을 제공한다. Cache 추상화는 Spring의 트랜잭션기능과 유사하게 코드의 변화를 최소화하면서 Proxy를 통해 동작하게끔 한다. Cache 구현체가 아닌 Cache추상화만을 제공하며 실제 Cache Data저장소는 EhCache와 ConcurrentMap을 지원한다.

설명

Cache를 설정하여 CacheManager를 통해 Cache에 접근하는 방법에 대하여 알아보고, 자바메소드를 Caching하는 @Cacheable에 대하여 알아본다.

Cache Configuration

EhCache

Spring에서는 EhCache를 지원하는 CacheManager로써 EhCacheCacheManager를 제공한다. <EhCache 설정>

<cache:annotation-driven cache-manager="cacheManager" />
<!-- EhCache를 저장소로 사용하는 Cache Manager -->
 <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcache"></property>
</bean>
<!-- Ehcache library setup -->
<bean id="ehcache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:config-location="classpath:springframework/cache/ehcache/ehcache.xml" />

Ehcache.xml에서 defaultCache저장소와 추가 Cache저장소의 설정을 한다. EhCAche는 비록 ConcurrentMap보다 속도는 느리지만 Cache관리기능 측면에서 유용하여 EhCache를 추천한다.

<ehcache.xml>

<ehcache> 
<defaultCache 
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            diskSpoolBufferSizeMB="30"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            statistics="false"
            />
<cache name="ehcache"
>

ConcurrentMap

Spring에서는 ConcurrentMap을 지원하는 SimpleCacheManager를 제공한다.

<CacheManager설정>

<cache:annotation-driven cache-manager="cacheManager"/>
 <!-- ConcurrentMap을 저장소로 사용하는 Cache Manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> 
   <property name="caches"> 
      <set> 
         <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>    
         <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>   
      </set> 
   </property> 
</bean>

Cache Annotation을 위한 annotation-driven 설정

Cache Annotation으로 Cache를 적용하기 위해서는 반드시 <cache:annotatioin-driven>을 붙여주어야한다. 이 네임스페이스는 AOP를 사용해서 캐시 기능을 다양한 방법으로 설정할 수 있는 옵션을 제공한다. 이 설정은 Transaction에 사용되는 <tx:annotation-driven>과 비슷하다.

속성 기본값 설명
cache-manager cacheManager 사용할 CacheManager의 이름. CacheManager의 Bean id가 cacheManager가 아닐 경우, 설정해야한다.
mode proxy 스프링 AOP를 사용하는 설정이며, “aspect”를 사용할 수도 있다.
proxy-target-class false false인 경우, JDK의 인터페이스 기반 프록시를 사용한다. true를 사용하면 클래스 기반 프록시를 사용한다.
order Ordered.LOWEST_PRECEDENCE @Cacheable/@CacheEvict메소드의 Cache advice가 적용되는 순서

CacheManager를 통한 Cache접근

ehCache, concurrentMap의 설정과 상관없이 코드상으로 동일한 CacheManager인터페이스로 bean을 주입받아 사용할 수 있다.

<CacheManager 의 사용>

import org.springframework.cache.CacheManager;
@Autowired 
private CacheManager cacheManager;
Cache cache = cacheManager.getCache("cache명");

@Cacheable

자바메소드에 @Cacheable을 설정함으로써 Caching할 수 있다. 타겟메소드가 호출되었을 때, 캐시에 해당 메서드가 이미 동일한 인자로 있는지 확인하고, 만약 있다면 메소드를 호출하지 않고 캐시해둔 결과를 Proxy에서 반환하게 된다.

Java Method에 적용가능한 Cache Annotation은 다음과 같다.

@Cacheable 사용방법

별다른 조건없이 호출되는 모든 인자를 caching하고자 할 때는 아래와 같이 cache명만 쓰면 된다. 이 코드는 Cache이름이 “books”인 cache저장소를 사용하였다. 메소드가 호출될 때 매번 “books”에 Cache데이터를 확인하고 이미 실행된 적이 있는지를 확인한다. 만약 “books”에 데이터가 있으면 그 값을 반환하게 된다.

기본
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

Cache되는 저장소를 여러개 정의할 수도 있다. 아래의 코드에서는 findBook메소드 호출 시 “books”와 “isbns” 두 군데에 캐시데이터가 저장된다.

<여러 cache 저장소에 caching>

@Cacheable({ "books", "isbns" })
public Book findBook(ISBN isbn) {...}
Cache Abstraction Key생성

Cache는 키-값으로 저장되며, 캐시된 메소드를 호출시마다 키를 통해 값을 가져오므로 캐시를 찾을 수 있는 키가 생성되어야한다. 별도의 커스텀키가 정의되지 않으면 default로 다음과 같은 알고리즘 기반의 KeyGenerator를 사용하여 Key를 생성한다.

이 외에 다른 기본키를 생성하려면 org.springframework.cache.KeyGenerator 인터페이스를 구현하면 된다.

Custom key 추가

@Cacheable을 적용한 메소드의 인자가 여러개일 때 Key로 사용할 것을 SpEL로 명시할 수 있다.

@Cacheable(value="books", key="#isbn"
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
 
 
@Cacheable(value="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
 
 
@Cacheable(value="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
Conditional 추가

Conditional을 주어서 그 값이 true이면 caching을 하고, false이면 caching을 하지 않기 때문에 호출시 매번 메소드내부가 실행된다. Conditional에서는 SpEL사용이 가능하며 Condition과 Unless를 쓸 수 있다. condition과는 달리 unless는 메소드의 결과값 반환 시점에 결과값을 확인하여 caching여부를 결정하게 된다.

@Cacheable(value="book", condition="#name.length < 32")
public Book findBook(String name)
@Cacheable(value="book", condition="#name.length < 32", unless="#result.hardback")
public Book findBook(String name)

Conditional에 쓰는 SpEL의 설명은 다음과 같다.

명칭 위치 설명 예제
methodName root객체 호출되는 메소드명 #root.methodName
method root객체 호출되는 메소드 #root.method.name
target root객체 호출되는 타겟오브젝트 #root.target
targetClass root객체 호출되는 타겟 클래스 #root.targetClass
args root객체 타겟을 호출 시 사용되는 인자들(배열) #root.args[0]
caches root객체 현재 메소드가 실행되는 캐시들의 집합 #root.caches[0].name
argument name 평가 context 메소드 인자명을 사용할 수 없을 때 대신 a<#arg>로 대체하여 사용할 수 있다. #arg는 0부터 시작하는 인자의 인덱스를 나타난다. iban 또는 a0 (p<#arg>로도 사용가능)
result 평가 context 메소드호출 결과. unless와 cache evict표현에서만 사용가능하다. #result

@CacheEvict

@CacheEvict는 @Cacheable과 반대로 cache저장소의 데이터를 제거함으로써 사용하지 않는 데이터를 정리하는데 유용하다. @CacheEvict는 캐시삭제를 수행할 메서드에 선언한다. @CacheEvict도 여러개의 캐시를 명시할 수 있으며, key와 condition을 사용할 수 있다. 또한 allEntries속성은 키값으로 Cache Entrie하나만 비우는 것이 아니라 캐시영역의 모든 Entrie를 비우도록 한다. 이 경우에는 키를 명시하더라도 이를 무시하고 모든 Entrie를 비우게 된다.

@CacheEvict(value = "books", allEntries=true)
public void loadBooks(InputStream batch)

@CachePut

메소드의 흐름을 방해하지 않고 Cache에 저장하거나 업데이트를 해야하는 경우, @CachePut을 사용한다. 즉, 메소드는 항상 실행되고 그 결과가 캐시에 저장된다. @CachePut은 @Cacheable과 동일한 옵션을 제공하며, Cache에 저장하는것보다는 메소드의 흐름을 최적화하는데 사용되어야 한다.

@Cacheable과 함께 사용하는것은 일반적으로 권장하지 않는다.

@Caching

다수의 Cache annotation을 쓰고자 할 때 @Caching을 쓴다. @Cacheable, @CacheEvic, @CachePut을 지원한다.

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(value = "secondary", key = "#p0") })
public Book importBooks(String deposit, Date date)

XML기반 Caching설정

Java메소드에 Annotation을 붙이는 대신 XML로 caching할 메소드를 정의할 수 있다. 아래에서는 CacheManager bean설정을 생략하였다. BookService패키지의 하위메소드 중 findBook 메소드에 cacheable이 적용되며 loadBooks 메소드에 cacheEvict가 적용되었다.

<!-- the service we want to make cacheable -->
<bean id="bookService" class="x.y.service.DefaultBookService"/>
 
<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
    <cache:caching cache="books">
        <cache:cacheable method="findBook" key="#isbn"/>
        <cache:cache-evict method="loadBooks" all-entries="true"/>
    </cache:caching>
</cache:advice>
 
<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>
...
// CacheManager설정 생략

참고자료