※ 사전지식
- JSR : (Java Specification Requests) 사양 및 기술적 변경에 대한 정식 제안 문서. 개인 및 조직은 JCP (Java Community Process)의 회원이 될 수 있으며 JSR에 언급 된 스펙에 따라 코드를 개발할 수 있다. 개발 된 기술적 변화는 JCP 회원들의 검토를 거쳐 승인된다.
.
- JSR-107 : (JCACHE – Java Temporary Caching API) 객체 생성, 공유 액세스, 스풀링, 무효화 및 JVM 전반에 걸친 일관성을 포함하여 Java 객체의 메모리 캐싱에서 사용할 API 에 대한 기준으로 볼 수 있다. 해당 Spec 으로 구현된 cache로는 EhCache가 유명하며, Hazelcast, Infinispan, Couchbase, Redis, Caffeine 등도 해당 기준을 따르는 것으로 알려져 있다.
> Cache의 활용
지금까지 경험했던 Site (주로 중대형 금융사)에서는 Cache의 활용이 거의 없었다. (계정계)라는 꽤나 변화하기 Cache도입이 쉽지 않은 (구)환경(Pro C, Mainframe (Cobol 등))의 원인도 있겠지만, Java Framework(Spring, Struts)을 이미 꽤 오래전에 도입한 중형 금융사들의 환경도 마찬가지 였다. 물론 Data변화가 잦은 Business 특성에는 맞지 않을 수 있지만, 그 가운데도 Cache를 도입할 요소는 분명 많이 있다고 생각된다. (예 : 코드 및 기준정보와 같은 Access가 잦으나 자주 Update되지 않는)
특히 공통으로 사용하는 코드정보는, 그 규모가 대형이 아닌 중형 금융사임에도 불구하고 하루 동안 Database Access (Select) 횟수가 10만회를 넘어 섰다. 이러한 경우 Database의 Cache가 분명 활용되었겠으나 Database의 Cache 및 리소스 활용을 더 중요한일에 할 수 없었던 아쉬운 경우가 아닌가 생각된다.
> SpringBoot에서의 Cache
기본적으로 (JSR-107) 에 따르는 Cache들을 지원한다. 또한 Spring은 각 Cache API들의 기술 및 변화에 관계 없이 일관된 사용을 위해 추상화를 지원한다(CacheManager) . Auto Detect 기능으로 인해 EhCache나 Redis등 별도 Cache library가 추가되면 해당 라이브러리를 자동적으로 이용하게 된다.
> Dependency 추가 (Maven)
SpringBoot Cache 및 사용할 Ehcache를 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.3</version>
</dependency>
> Ehcache config (XML) 파일 생성 (ehcache.xml)
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd"
updateCheck="true"
monitoring="autodetect"
dynamicConfig="true">
<cache name="사용할CacheName"
maxElementsInMemory="1000"
eternal="true"
overflowToDisk="false"
timeToLiveSeconds="300"
timeToIdleSeconds="0"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
</cache>
</ehcache>
> name : 캐시 이름 지정
> maxEntriesLocalHeap: 메모리에 생성될 Entry Max값 (0=제한없음)
> maxEntriesLocalDisk: 디스크(DiskStore)에 저장될 Entry Max값 (0=제한없음)
> eternal: 영구 Cache 사용 여부 (true 인경우 timeToIdleSeconds, timeToLiveSeconds 설정은 무시된다.)
> timeToIdleSeconds: 해당 시간 동안 캐쉬가 사용되지 않으면 삭제. (0=삭제되지 않는다)
> timeToLiveSeconds: 해당 시간이 지나면 캐쉬는 삭제된다. (0=삭제되지 않는다)
> diskExpiryThreadIntervalSeconds: DiskStore 캐시 정리 작업 실행 간격 (Default=120초)
> diskSpoolBufferSizeMB: 스풀버퍼에 대한 DiskStore 크기 설정
> clearOnFlush: flush() 메서드 호출 시점에 메모리(MemoryStore) 삭제 여부. (Default=true)
> memoryStoreEvictionPolicy : maxEntriesLocalHeap 설정 값에 도달했을때 설정된 정책에 따라객체가 제거되고 새로 추가된다.
> logging: 로깅 사용 여부를 설정한다.
> maxEntriesInCache: Terracotta의 분산캐시에만 사용가능하며, 클러스터에 저장 할 수 있는 최대 엔트리 수를 설정한다. 0은 제한이 없다. 캐시가 작동하는 동안에 속성을 수정할 수 있다.
> overflowToOffHeap: 이 설정은 Ehcache 엔터프라이즈 버전에서 사용할 수 있다. true 로 설정하며 성능을 향상시킬 수 있는 Off-heap 메모리 스토리지를 활용하여 캐시를 사용할 수 있다. Off-heap 메모리 자바의 GC에 영향을 주지않는 다. (Default=false)
(참고사이트 : http://www.ehcache.org/ehcache.xml)
> SpringBoot Property 파일 수정 (application.properties)
...
spring.cache.ehcache.config=classpath:ehcache.xml
...
> Cache Enable (@EnableCaching 어노테이션 추가)
@EnableCaching
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
※ 만약 추가된 별도 Cache (Ehcache)가 없을 경우 Cache Enable되면 ConcurrentMap 을 이용하는 방식으로 Cache가 Enable 된다.
> 어노테이션을 통한 Cache 사용
- @Cacheable
@Cacheable(cacheNames = "useCacheNames", key="#pageNo")
public List<ObjectA> getEventsByPageNo(int pageNo) {
.......
}
메서드를 기준으로 수행되며, 2가지의 캐쉬기능이 동작한다. 1. 해당 메서드의 Argument, 혹은 그 중 일부를 key로 취하여 캐쉬에 해당 Key가 존재할 경우 메서드는 수행되지 않고 캐쉬 값을 바로 return 값으로 반환 한다. 2. 또한 캐쉬에 해당 값이 존재 하지 않아 메서드가 수행될 경우 Key와 수행결과 (return 값)을 캐쉬에 저장한다.
– 어노테이션 Parameter
> cacheNames, value : 사용할 Cache 명을 지정한다. cache설정 파일(ehcache.xml)에서 생성해준 Cache 명을 지정한다. 배열 형식({A, B, C}) 등으로 명시하여 여러개의 Cache에 적용토록 사용할 수도 있다.
> key : cache key로 사용할 key를 명시적으로 지정한다. 해당 메서드의 Argument 중 일부를 지정할 수도 있고, keyGenerator를 사용하도록 지정할 수도 있다. 혹은 문자열의 조합을 사용하도록 지정도 가능하다.
> condition : true나 false가 되는 SpEL 표현식을 받는 conditional 파라미터로 캐시수행 여부를 결정하는 조건을 지정할 수 있다.
(예: condition=”#name.length < 32″)
> unless : SpEL표현식, 값이 true이면 반환 값이 캐시에 남지 않게 된다.
> 어노테이션의 Parameter명 지정 없이 @Cacheable(“cacheNM”) 와 같이 사용할 경우 cacheNames와 같다.
> Key가 지정되지 않은 상태로 해당 어노테이션이 사용될 경우 메서드가 단일 Argument일 경우 자동으로 해당 값을 취하며, 여러개의 Argument가 존재할 경우 SimpleKey의 형태로 자동으로 복합키를 생성하여 취하도록 수행된다.
- @CacheEvict
@CacheEvict(cacheNames = {"cacheNameA"}, allEntries = true)
public void clearCache(){}
저장된 캐쉬를 제거한다. @Cacheable와는 달리 @CacheEvict 어노테이션은 캐시를 제거(eviction)하는 메서드를 구분하는데 즉, 캐시에서 데이터를 제거하는 트리거로 동작하는 메서드다. 다른 캐시 어노테이션과 마찬가지로 @CacheEvict는 동작할 때 영향을 끼치는 하나 이상의 캐시를 지정해야 한다.
– 어노테이션 Parameter
> cacheNames, value, key, condition : @Cacheable 과 동일
> allEntries : @CacheEvict에서 키나 조건을 지정해야 할 수 있지만 딱 하나의 엔트리(키에 기반을 둔)가 아니라 제거를 할 캐시의 범위를 나타내는 allEntries 파라미터를 추가로 사용할 수 있다. 해당 값을 true로 할 경우 해당 CacheName에 해당하는 모든 캐쉬를 비운다.
> beforeInvocation : 메서드 실행 이후(기본값)나 이전에 제거를 해야 하는 지를 지정할 수도 있다. false 일 경우 메서드가 실행되지 않거나(캐시 되어서) 예외가 던져지면 제거가 실행되지 않는다. true로 지정할 경우 메서드 정상 수행 여부와 관계 없이 항상 캐쉬 제거가 수행된다.
- @CachePut
@CachePut(cacheNames = "useCacheNames", key="#pageNo")
public List<ObjectA> getEventsByPageNo(int pageNo) {
.......
}
메서드를 수행 후 Return 값을 캐쉬에 저장한다. @Cacheable과의 차이는 캐싱값을 취하는 (메서드가 수행되지 않는) 방법이 아닌 메서드 수행 후 항상 캐쉬값을 저장 하도록 한다는 것이다.
– 어노테이션 Parameter : @Cacheable 과 동일
- @Caching
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(value = "secondary", key = "#p0") })
public Book importBooks(String deposit, Date date)
@CacheEvict나 @CachePut처럼 같은 계열의 어노테이션을 여러 개 지정해야 하는 경우가 있는데 예를 들어 조건이나 키 표현식이 캐시에 따라 다른 경우이다. 안타깝게도 자바는 이러한 선언을 지원하지 않지만 감싸진(enclosing) 어노테이션을 사용해서(이 경우에는 @Caching) 우회할 수 있다. @Caching에서 중첩된 @Cacheable, @CachePut, @CacheEvict를 같은 메서드에 다수 사용할 수 있다.
> ehCacheManager를 통한 Cache 사용
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.Cache;
.....
@Autowired
private EhCacheCacheManager ehCacheManager;
.....
public void setCacheByEvents(String stDate) {
Cache ehCacheAll = ehCacheManager.getCache("cacheName");
ehCacheAll.put("keyA", 56789945);
......
}
자동 Detecting 되어 생성된 Bean – EhCacheCacheManager (혹은 CacheManager)를 주입 받아 해당 Cache를 수동으로 생성 혹은 삭제할 수도 있다. 어노테이션에 의한 방법의 경우 캐쉬값의 생성에 있어서는 최초에 한번 put 해주는 작업이 필요하며 key가 low 레벨일 경우 번거로움을 수반한다. (예 : Batch 혹은 Bean Construct 후 Cache 값 셋팅 등)
아래의 코드와 같이 현재 생성된 Cache값을 모두 Print 해볼수도 있다.
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.Cache;
.....
@Autowired
private EhCacheCacheManager ehCacheManager;
.....
public void printAllCache() {
String[] cacheNames = ehCacheManager.getCacheManager().getCacheNames();
for(String cacheName : cacheNames){
List strArr = ehCacheManager.getCacheManager().getCache(cacheName).getKeys();
for (Object string : strArr) {
System.out.println(cacheName + "-Member : "+string.toString());
}
}
}