ehcache는 Spring에서 사용할 수 있는 Java 오픈소스 캐시 라이브러리이다.

ehcache 특징

  • Redis 같은 캐시 서버를 요구하지 않는다.
  • Spring 내부 동작으로 캐시를 처리한다.
  • 3버전 부터 javax.cache.API와의 호환성을 제공한다.
  • 3버전 부터 offheap 메모리 공간을 제공해 Java GC에 탐색되지 않는다.
  • 저장할 데이터가 Serializable을 상속받은 클래스여야 한다.

Off-Heap Stroe

자바 힙 밖에 저장한다라는 의미를 가지고 있다. Direct ByteBuffer를 통해 할당받은 버퍼 공간은 GC의 대상이 되지 않는다. off-heap store에 저장되는 객체는 직렬화(Serialize)한 다음에야 저장이 된다. 이러한 이유로 Allocation/Deallocation 비용이 일반 버퍼에 비해 높다. 때문에 off-heap store에 저장될 객체는 사이즈가 크고 오랫동안 메모리에 살아있고 시스템 네이티브 I/O 연산의 대상이 되는 객체를 사용하는 것이 좋다.

캐싱 메소드 내부에서 캐싱 메소드를 호출한다면?

캐시와 트랜잭션 처리는 Spring AOP를 활용해서 제공되는 기능이다. 그렇기 때문에 Spring AOP 특성과 제약을 그대로 이어받는데 AOP는 실제 메소드를 호출하는 것이 아닌 Proxy 객체를 거쳐 타깃메소드가 호출되는 것이다. 해당 메소드를 call하게 되면(self-invocation) proxy interceptor를 타지 않고 바로 메소드를 호출하기 때문에 캐싱이 되지 않는다. 또한 private final이 적용된 메소드도 Proxy 객체를 상속받을 수 없기 때문에 동작하지 않는다. 하고싶다면 AspectJ라는 AOP를 지원해주는 라이브러리를 사용해야한다.

ehcache 설정

<!-- src/main/resources/ehcache.xml -->

<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns="http://www.ehcache.org/v3">
    <persistence directory="cache/data"/>

    <!-- 캐시 공통 설정 -->
    <cache-template name="defaultTemplate">
        <!-- 캐시 만료 시간 설정 -->
        <expiry>
            <ttl unit="seconds">30</ttl>
        </expiry>
        <resources>
            <!-- 최대 캐싱갯수(LRU) -->
            <heap unit="entries">100</heap>
            <!-- offHeap 영역크기 -->
            <offheap unit="MB">10</offheap>
        </resources>
    </cache-template>

    <!-- 캐시 이름 설정 -->
    <cache alias="argCache">
        <!-- 캐시 키 설정 (메소드 인자) -->
        <key-type>java.lang.String</key-type>
        <!-- 캐시 결과값 설정 -->
        <value-type>java.lang.String</value-type>
        <expiry>
            <ttl unit="seconds">30</ttl>
        </expiry>
        <resources>
            <heap unit="entries">10</heap>
            <offheap unit="MB">1</offheap>
        </resources>
    </cache>

    <!-- 캐시 템플릿 사용 -->
    <cache alias="testCache" uses-template="defaultTemplate">
        <value-type>java.lang.String</value-type>
    </cache>
</config>
// Cache를 사용하기 위해 EnableCaching 어노테이션을 사용하면 애플리케이션이 로드될때 ehcache.xml을 읽어 초기화 및 캐시 기능을 사용할 수 있게됨
@EnableCaching
@SpringBootApplication
public class EhcacheTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(EhcacheTestApplication.class, args);
    }
}
@Service
public class Factory {
    private String field = "";
    private String argField = "";

    /**
    * 사용하려는 메소드에 @Cahceable 사용
    * value에 ehcache의 key, value와 일치하는 캐시 이름을 사용
    */
    @Cacheable(value = "testCache")
    public String test() {
        field += "A";
        return field;
    }

    /**
    * key에 캐시 key타입과 메소드 인자 타입과 일치하는 #필드명을 추가
    */
    @Cacheable(value = "argCache", key = "#arg")
    public String argCache(String arg) {
        argField += arg;
        return argField;
    }
}

캐싱 사용 결과

void test1() {
        System.out.println("+= 'A' 메소드 호출");
        System.out.println(factory.test());

        System.out.println("+= 'A'를 하는 메소드를 한번더 호출했지만 지난 결과 캐싱됨");
        System.out.println(factory.test());
}

image

void argCache() {
        System.out.println("argCache('A') 메소드 호출");
        System.out.println(factory.argCache("A"));

        System.out.println("argCache('B') 메소드 호출");
        System.out.println(factory.argCache("B"));

        System.out.println("argCache('A') 메소드 호출");
        System.out.println(factory.argCache("A"));

        System.out.println("argCache('B') 메소드 호출");
        System.out.println(factory.argCache("B"));
    }

image

출처

Ehcache

[Spring] ehCache2와 달라진 ehCache3 사용 (tistory.com)

Springboot EhCache 3 - 환경설정부터 self-invocation 처리까지 (tistory.com)