JAVA : SpringBoot2 에 Ehcache3 적용하기

Srping 에 Cache 를 도입하면 메소드 레벨로 캐싱을 할 수 있다. 코드목록 가져오는 것 처럼 데이터가 자주 바뀌지 않지만 매우 자주 호출되는곳에 적용하면 좋을것 같다.

maven dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.1.1</version>
</dependency>
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.8.0</version>
</dependency>

@EnableCaching 어노테이션 추가

요런식으로 적당한 곳에 추가해 주면 된다.

@SpringBootApplication
@EnableCaching
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

캐시를 적용할 곳에 @Cacheable, @CacheEvict 어노테이션 추가

어노테이션이 몇개 더 있는데 대충 이것 두개만 써도 충분할것 같다. 샘플로 코드 가져오는 서비스 클래스에 적용해 보았다.

코드리스트 가져오는 메소드가 가장 많이 호출되기 때문에 굳이 다른 메소드는 캐쉬를 적용안해도 될 것 같다.

캐쉬를 적용할 메소드 위에 딸랑 @Cacheable 요 어노테이션만 살짝 설정해 주면 끝난다. 매우 간단하지 아니한가? 🤗

그리고 코드가 변경되거나 삭제됐을때는 캐쉬를 지워야 되기 때문에 해당 메소드에 @CacheEvict 를 적용하면 된다.

package io.github.stove99.sample;

import java.util.List;
import java.util.Map;

import io.github.stove99.sample.module.code.mapper.CodeMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author stove99
 */
@Service
@Transactional
public class CodeService {

    @Autowired
    private CodeMapper mapper;

    public Map<String, String> get(String code) {
        return mapper.select(code);
    }

    /**
    * value 는 캐쉬 뭉탱이의 이름이라고 생각하면 된다. (RDBMS 의 테이블)
    * key 는 RDBMS 의 테이블의 PK 라고 생각하면 됨
    * list("test") 를 호출하면 메소드가 한번 실행된 후에 리턴되는 List 가 codes 에 test 키에 캐쉬된다.
    * 다시 똑같은 파라메터로 list("test") 호출하면 메소드가 실행되지 않고 codes 캐쉬에 있는 test 키를 찾아서 저장된 값을 리턴한다.
    **/
    @Cacheable(value = "codes", key="#codeGrp")
    public List<Map<String, String>> list(String codeGrp) {
        return mapper.list(codeGrp);
    }

    /**
    * 데이터가 변경되면 캐시되 있던걸 지워야 됨
    * @CacheEvict(value = "codes", allEntries = true) 는 codes 캐시에 들어있는걸 싹 지운다는 뜻
    *
    * 특정 코드그룹 key 만 캐쉬에서 지우고 싶으면
    * @CacheEvict(value = "codes", key = "#data.get('codeGrp')") 요런식으로 쓰면 된다.
    *
    * getter 가 있는 일반 객체는 요렇게
    * @CacheEvict(value = "codes", key = "#data.codeGrp")
    **/
    @CacheEvict(value = "codes", allEntries = true)
    public Map<String, String> save(Map<String, String> data) {
        if (get(data.get("code")) == null) {
            mapper.insert(data);
        } else {
            mapper.update(data);
        }
        return data;
    }

    @CacheEvict(value = "codes", allEntries = true)
    public void remove(String code) {
        mapper.delete(code);
    }
}

ehcache.xml 설정파일 추가

죠렇게 딸랑해주면 좋겠지만 ehcache 로 캐싱을 할려면 ehcahce 설정을 쫌 해줘야 된다.

/src/main/resources/ehcache.xml 에 파일을 추가해 준다.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.ehcache.org/v3"
    xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
    xsi:schemaLocation="
            http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
            http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">

    <!--
        캐쉬가 하나만 있으면 굳이 템플릿을 안 맹글어도 되지만 여러군데 쓸 수 있기 때문에
        중복되는것을 피하기 위해 템플릿을 맹금
    -->
    <cache-template name="template">
        <!-- 캐시 만료기간 설정 -->
        <expiry>
            <!-- unit : nanos, micros, millis, seconds, minutes, hours, days -->
            <ttl unit="days">1</ttl>
        </expiry>

        <!--
        캐시가 생성되고 삭제되고 하는 이벤트를 모니터링 하고 싶으면
        org.ehcache.event.CacheEventListener 를 구현하는 클래스를 만들어서 요렇게 설정해 주면됨
        태그 순서가 중요해서 아무데나 listeners 태그를 추가하면 오류남
        <listeners>
            <listener>
                <class>io.github.stove99.sample.CacheEventLogger</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <event-ordering-mode>UNORDERED</event-ordering-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>EVICTED</events-to-fire-on>
                <events-to-fire-on>REMOVED</events-to-fire-on>
                <events-to-fire-on>UPDATED</events-to-fire-on>
                <events-to-fire-on>EXPIRED</events-to-fire-on>
            </listener>
        </listeners>
        -->

        <resources>
            <!-- 캐시에 최대 몇개 까지 유지할지 -->
            <heap unit="entries">200</heap>
        </resources>
    </cache-template>

    <!--
        alias 는 @Cacheable(value = "codes", key="#codeGrp") 여기에서 썻던 value 값으로
    -->
    <cache alias="codes" uses-template="template"></cache>
    <cache alias="menus" uses-template="template" >
        <expiry>
            <ttl unit="seconds">60</ttl>
        </expiry>
    </cache>
    <cache alias="contents_pages" uses-template="template"></cache>
</config>

application.properties 또는 application.yml 설정

spring.cache.jcache.config: classpath:ehcache.xml
spring:
    cache:
        jcache:
            config: classpath:ehcache.xml

끝~