[스프링 3.1 Cache] 테스트하기

캐시 기능을 테스트 하는 방법을 간단하게 소개합니다.

캐시에 들어있는지 없는지 확인하면 되겠죠. 쉽게, CacheManager라는 것을 사용하면 됩니다. 어차피 스프링 캐시를 사용하려면 등록해야 하는 빈입니다. CacheManager는 인터페이스이고, 사용하려는 캐시에 따라 다른 CacheManager 빈을 등록해야 하죠. EhCache를 사용한다면, EhCacheCacheManager를 등록해야 합니다.

스프링 레퍼런스에도 EhCache를 사용하는 경우의 예제 코드를 제공하고 있는데요. 지금 스프링 레퍼런스 문서에 있는 코드는 소소한 문제가 있습니다. 버그 리포팅을 했으니 조만간 고쳐질 겁니다.

[xml]
<cache:annotation-driven />
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cacheManager-ref="ehcache"/>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configLocation="classpath:/ehcache.xml"/>
[/xml]

이런식으로 빈을 등록하게 되지요. 여기서 cachaManager라는 빈 id는 cache:annotation-driven에서 사용하는 기본값이기 때문에 되도록이면 cacheManager라고 id를 적어주는게 좋겠습니다. 물론 원하는 이름을 적은 뒤에 cache:annotation-driven에 cache-manager라는 속성에 명시해주어도 됩니다.

그리고 테스트는 이렇게 할 수 있습니다.

[java]
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
public class MemberServiceImplTest {

@Autowired MemberService service;
@Autowired CacheManager cacheManager;

@Test
public void getMemberCache(){
//GIVEN
Cache memberCache = cacheManager.getCache("member");

//WHEN
service.getAMember(1);
//THEN
assertThat(memberCache.get(1), is(notNullValue()));

//WHEN
service.getAMember(2);
//THEN
assertThat(memberCache.get(2), is(notNullValue()));

//WHEN
service.getAMember(3);
//THEN
assertThat(memberCache.get(3), is(notNullValue()));
}
[/java]

참 쉽죠?

스프링 3.1 Cache Abstraction

http://static.springsource.org/spring/docs/3.1.0.M2/spring-framework-reference/html/cache.html

28. 캐시 추상화

28.1. 소개

스프링 프레임워크 3.1부터 기존의 스프링 애플리케이션에 투명하게 캐싱을 추가할 수 있는 기능을 제공한다. 트랜잭션 기능과 비슷하게, 캐싱 추상화도 최소한의 코드로 다양한 캐싱 솔루션을 사용할 수 있게 해준다.

28.2 캐싱 추상화 이해

자바 메서드에 캐싱 추상화를 적용한다는 것은, 캐시에 있는 정보를 이용해서 메서드 실행 횟수를 줄인다는 것이다. 다시 말해서, 타겟 메서드가 호출 됐을 때, 캐시 추상화 계층이 해당 메서드가 이미 동일한 인자로 실행된 적이 있는지 확인한다. 만약 그런 적이 있다면, 메서드를 호출하지 않고 캐시해둔 결과를 반환한다. 그런 적이 없다면, 메서드를 실행하고 그 결과를 캐시하고 반환한다. 이런 방법으로 (CPU나 IO)비용이 큰 메서드를 매개변수 마다 한번만 실행되도록 할 수 있다.

주의

물론, 이런 접근 방법은 얼마나 여러번 호출하든지 상관없이, 입력(메서드 인자)에 따른 결과(메서드 반환값)가 동일하다는 보장이 되는 메서드에 적용해야 한다.

캐시 추상화를 사용하려면, 개발자는 두 가지를 알아야 한다.

  • 캐시 선언 – 캐시를 적용할 메서드를 식별하고, 그곳에 적용할 캐시 정책을 설정하는 방법
  • 캐시 설정 – 데이터를 저장하고 읽어올 캐시 저장소에 대한 설정

스프링 프레임워크가 제공하는 다른 서비스와 마찬가지로, 캐시 서비스도 추상화기 때문에, 실제로 캐시 데이터 저장소를 사용해야 한다. 즉, 추상화로 인해서 개발자가 캐싱 로직을 작성해야 하는 수고를 덜 수 있지만 실제 저장소를 제공하지는 않는다. 현재, 두 가지 구현체를 제공한다.

  • JDK, java.util.concurrent.ConcurrentMap
  • Ehcache

28.3 선언적인 애노테이션 기반 캐싱

캐시를 선언할 때는 @Cacheable과 @CacheEvict를 사용해서 캐시를 만들고 캐시를 정리한다. 각 애노테이션을 자세히 살펴보자.

28.3.1 @Cacheable 애노테이션

애노테이션 이름이 암시하듯이, @Cacheable은 캐시할 메서드를 선언할 때 사용한다. 즉, 메서드 결과가 캐시에 저장되고, 동일한 인자로 메서드를 호출하면, 메서드를 실제로 호출하지 않고 그 값을 반환한다. 가장 간단히, 애노테이션을 붙이 메서드와 연관된 캐시 이름을 사용해서 선언할 수 있다.

[java]
@Cacheable("books")
public Book findBook(ISBN isbn) {…}
[/java]

이 코드는 findBook과 관련된 캐시 이름 books를 사용했다. 매번 이 메서드가 호출될 때, 캐시를 확인하고 이미 실행된 적이 있는지 반복할 필요가 없는지 확인한다. 대부분 캐시를 하나만 선언하겠지만, 이 애노테이션에 사용할 캐시 이름을 여러개 명시할 수도 있다. 그럴 경우에는 메서드를 실행하기 전에 모든 캐시를 확인한다. 최소한 하나의 캐시에서 필요한 데이터를 발견하면, 그 값을 반환한다.

노트

메서드를 담고 있지 않은 다른 모든 캐시도 수정된다.

[java]
@Cacheable({ "books", "isbns" })
public Book findBook(ISBN isbn) {…}
[/java]

28.3.1.1 기본 키 생성

캐시는 기본적으로 키-값 스토어기 때문에, 캐시된 메서드를 호출할 때 마다 캐시를 찾을 수 있는 적절한 키로 변환할 필요가 있다. 기본적으로 캐시 추상화는 다음과 같은 알고리즘 기반의 KeyGenerator를 사용한다.

  • 매개변수가 아무것도 없으면 0을 반환한다.
  • 매개변수가 하나면, 그 인스턴스를 반환한다.
  • 매개변수가 둘 이상이면, 모든 매개변수의 해시로 계산한 키를 반환한다.

hashCode()를 잘 만들었다면, 자연키를 가지고 있는 객체에 잘 적용된다. 분산 환경이나, 영속 환경에서는 이런 방식이 적합하지 않을 수 있는데, 객체의 hashCode를 사용하지 않도록 바꿀 필요가 있다. 사실, JVM 구현체나 실행 환경에 따라서, 동일한 VM 인스턴스에서 다른 객체가 동일한 hashCode를 만드는 경우도 있다.

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

28.3.1.2 커스텀 키 생성 선언

캐싱을 적용할 때, 타겟 메서드에 인자가 여러개 일 때, 캐시키로 사용할 것을 SpringEL을 사용해서 명시할 수도 있다. 매개변수 중에서 캐시와 관련 있는 것은 일부일 뿐이고, 나머지는 메서드 로직에서만 사용되는 경우가 그렇다.

[java]
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed
[/java]

key 속성을 사용해서 SpEL로 관심있는 인자를 선택할 수도 있고, 오퍼레이션을 수행하거나, 임의 메서드를 호출할 수도 있다.

[java]
@Cacheable(value="book", key="#isbn"
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(value="book", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(value="book", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
[/java]

28.3.1.3. 조건적인 캐싱

항상 캐시를 적용하는 것이 아니라, 인자 값의 조건에 따라 캐싱해야 할 경우가 있다. 캐시 애노테이션은 conditional 속성과 SpEL 표현식으로 ture나 false 평가식을 만들 수 있다. 해당 표현식이 true면 캐싱하고, false명 캐싱하지 않는다.

[java]
@Cacheable(value="book", condition="#name.length < 32")
public Book findBook(String name)
[/java]

28.3.1.4. SpEL 표현식에서 사용할 수 있는 컨텍스트

다음 목록은 key와 conditional에서 사용할 수 있는 SpEL 컨테스트다.

표 28.1. Cache SpEL available metadata

이름 위치 설명 예제
methodName root 객체 실행될 메서드 이름 #root.methodName
method root 객체 실행될 메서드 #root.method.name
target root 객체 실행될 타겟 객체 #root.target
targetClass root 객체 실행될 타겟 클래스 #root.targetClass
params root 객체 타겟을 실행할 때 사용하는 인자 목록 #root.params[0]
cacahes root 객체 현재 메서드 실행할 때 사용하는 캐시 컬렉션 #root.cacahes[0].name
parameter name evalucation 컨텍스트 메서드 매개변수 이름 iban 또는 p0

 

28.3.2 @CacheEvict 애노테이션

캐시 추상화는 캐시 생성 뿐 아니라 삭제로 제공한다. 이 과정은 변한 데이터나 사용하지 않는 데이터를 캐시에서 제거할 때 유용하다. @Cacheable과는 반대로 @CacheEvict는 캐시 삭제를 수행할 메서드에 선언한다. 캐시의 데이터를 지울 트리거 역할을 할 메서드에 사용한다. @CacheEvict도 여러 캐시를 명시할 수 있으며, key와 condition을 사용할 수 있다. 거기에 추가로, allEntries 속성도 있는데, 이 속성은 키값으로 캐시 엔트리 하나만 비우는 것이 아니라 캐시-영역을 비우도록 한다.

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

이 옵션은 캐시 영역을 비워야 할 때 유용하다. 이 경우에는 키를 명시해도 적용되지 않기 때문에 무시한다.

28.3.3 캐시 추상화 사용하기

캐시 애노테이션을 붙인다고 해서 모두 처리되지는 않는다. 캐시 기능을 사용하도록 한 줄을 추가해야 한다.

[xml]
<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 />
</beans>
[/xml]

이 네임스페이스는 AOP를 사용해서 캐시 기능을 다양한 방법으로 설정할 수 있는 옵션을 제공한다. 설정은 tx:annotation-driven과 비슷하다.

표 28.2 <cache:annotation-driven/> 설정

속석 기본값 설명
cache-manager cacheManager 사용할 캐시 매니저 이름. 캐시 매니저 이름이 cacheManager가 아닐 경우에만 설정.
mode proxy 스프링 AOP를 사용하는 설정이며, “aspect”를 사용할 수도 있다.
proxy-target-class false JDK의 인터페이스 기반 프록시를 사용하도록 한다. true를 사용하면 클래스 기반 프록시를 사용한다.
order Ordered.LOWEST_PRECEDENCE 캐시 어드바이스가 적용되는 순서.

 

노트

<cache:annotation-driven/>은 오직 동일한 애플리케이션 컨텍스트 내부에 있는 빈의 @Cacheable/@CacheEvict 애노테이션을 처리한다.

28.3.4 커스텀 애노테이션 사용하기

캐시 추상화는 커스텀 애노테이션을 사용해서 캐시를 만들거나 제거할 수 있게 해준다. 캐시 중복 선언을 제거할 때 유용하다.

[java]
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(value=“books”, key="#isbn")
public @interface SlowService {
}
[/java]

SlowService 애노테이션은 그 자체로 @Cacaheable 애노테이션이기 때문에 다음과 같이 교체할 수 있다.

[java]
@Cacheable(value="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
[/java]

또는

[java]
@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
[/java]

이렇게 @SlowService가 스프링 애노테이션은 아니지만, 컨테이너에서 자동으로 읽어서 처리해준다.

28.4 캐시 저장소 설정

기본적으로 두가지 저장소를 지원한다. 하나는 JDK ConcurrentMap이고 다른 하나는 ehcache 라이브러리다. 이것을 사용하려면 적절하게 CacheManager를 선언해야 한다.

28.4.1 JDK ConcurrentMap 기반 캐시

JDK 기반 Cache 구현체는 org.springframework.cacahe.cocurrent 패키지에 있다. ConcurrentHashMap을 캐시 저장소로 사용하는 구현체다.

[xml]
<!– generic 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>
[/xml]

여기서는 SimpleCacheManager가 default와 books라는 ConcurrentMapCache 구현체를 만들도록 설정했다.

애플리케이션에서 캐시를 만들면 그 캐시의 라이프사이클이 애플리케이션에 종속된다. 매우 빠르지만 관리 기능이나 영속화 기능은 제공하지 않는다.

28.4.2 Ehcache 기반 캐시

Ehcache 구현체는 org.springframework.cache.ehcache 패키지에 있다. 이것을 사용하려면 간단하게 적절한 CacheManager를 설정하면 된다.

[xml]
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhcacheCacheManager" p:cache-manager="ehcache"/>

<!– Ehcache library setup –>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>
[/xml]

ehcache 관련 설정은 ehcache.xml에 있다.

28.5 다른 백엔드 캐시 사용하기

분명히 여러 캐싱 제품이 있고 그것을 캐시 저장소로 사용할 수 있다. 그런 것을 사용하려면, CacheManager와 Cache 구현체를 만들어야 한다. 어렵게 들리겠지만, 실제로는 간단한 어댑터를 만드는 것이다. CacheManager 클래스는 org.springframework.cache.support 패키지에 있는 것을 사용할 수 있다. 그 중에서 AbstractCacheManager는 실제 맵핑을 할 때 자주 반복되는 코드를 줄여줄 수 있다.

스프링 3.1 m2

http://blog.springsource.com/2011/06/09/spring-framework-3-1-m2-released/

스프링 3.1 M2가 나온지 조금 됐습니다. 저 글에서는 RC1을 7월에 내놓고 9월에 GA 릴리즈를 하겠다고 했었지만, 아시다시피 스프링은 배포 일정을 항상 굉장히 긍정적으로 말해주기 때문에 최소한 +2~3 달 정도를 하시면 얼추 비슷하게 나옵니다. 그래서 제 예상에는 RC1이 9월쯤 나올 것 같구요. GA는 빠르면 12월쯤이 되야 나올 것 같습니다.

스프링 3.1 M2는 스프링 3.1 M1에서 추가한 주요 기능의 완성도를 높였다고 합니다.

– Environment 추사화를 안정화 했으니 사용해 보시라.
– @Feature 말고 @Enable* 애노테이션으로 자바 기반 설정 기능 강화.

두 기능 모두 말 그대로 완성 버전이라고 생각됩니다. Environment 추상화와 가자 기반 설정 모두 3.1의 핵심 기능인 만큼 나중에 조금 더 자세히 소개드리겠습니다.

새로 추가한 기능도 있습니다.

– 서블릿 3.0 기반 initializer: 이걸로 이제 tomcat 7 같은 servlet 3.0을 지원하는 컨테이너에는 web.xml 없이 자바 파일만 가지고 스프링 웹 애플리케이션을 배포할 수 있습니다.

– JPA 사용할 때도 packagesToScan 지원. 그래!! 바로 이거에요. Hibernate SessionFactory를 설정할 때는 저 옵션이 있는데, EntityManagerFactory를 설정할 때는 저 옵션이 없어서 굉장히 불편했지요.

– 핸들러 메서드 기반의 RequestMappingHandlerAdapter. 이것도 꼭 필요한 것이죠. 핸들러 단위가 클래스에서 메서드로 바뀌었기 때문에 이런게 필요합니다.

이밖에…

– “c” 네임스페이스 추가.

– 테스트 콘텍스트에서 @Configuration과 Environment 프로파일 지원

– REST 지원 기능 강화

정말 하나같이 다 주옥같은 업데이트로군요.

[스프링 3.1] 캐시 추상화

http://www.javacodegeeks.com/2011/02/spring-31-cache-abstraction-tutorial.html

http://static.springsource.org/spring/docs/3.1.0.M1/spring-framework-reference/html/cache.html

1. 캐시 선언

2. 캐시 설정

캐시 선언

1. @Cacheable

캐시할 메서드 위에 선언하면 된다. 그런데 key값을 무조건 메서드의 모든 매개변수의 해시 코드 값을 사용하기는 뭐하다. 내가 사용하고 싶은 속성만 사용해서 key값을 정하고 싶다. 그럴 수 있다. key라는 속성에 SpEL을 사용하면 된다.

[java]
@Cacheable(value="book", key="isbn"
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(value="book", key="isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(value="book", key="T(someType).hash(isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
[/java]

@Cacheable의 key에서 사용할 수 있는 SpEL에서 참조할 수 있는 객체

– methodName | @Cacheable을 호출한 메서드 이름 | #root.methodName

– caches | 현재 메서드 실행 했을 때의 캐시 컬렉션 | #root.caches[0].name

– 매개변수 이름 | 메서드 매개변수에 선언되어 있는 변수명 | 암거나..

그리고 메서드 호출될 때마다 캐시로 저장하지 말고, 특정 조건을 만족할 때만 캐시하고 싶다. 그럴 수 있다. condition이라는 속성을 사용하면 된다.

[java]
@Cacheable(value="book", condition="name.length < 32")
public Book findBook(String name)
[/java]
2. @CacheEvict

캐시에서 빼고 싶을 때 사용하고, 속성은 @cacheable과 비슷하다. allEntries가 하나 더 있는데, 특정 캐시 영역을 전부 날리고 싶을 때 사용하면 된다.

@CacheEvict(value=”books”, allEntires=true)

캐시 설정

애노테이션은 선언일 뿐, 선언해둔다고 다 되진 않는다. 선언을 읽어서 실제 처리를 할 빈을 등록해야 함. cache라는 네임스페이스 추가해뒀기 때문에 편하게 설정할 수 있다.

[xml]
<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.xsdhttp://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven />
[/xml]
현재 기본 구현체로는 ConcurrentHashMap 기반의 구현체를 사용하며, Ehcache 기반 구현체를 스프링에서 따로 제공한다. Ehcache 기반의 구현체를 사용하고 싶다면 다음과 같이 설정하면 된다.

[xml]
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhcacheCacheManager" p:cache-manager="ehcache"/>

<!– Ehcache library setup –>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>
[/xml]