Spring Framework 4.1 부터 JSR-107 annotations 지원 및 커스터이징 옵션이 크게 확장 됨
8.1. Understanding the Cache Abstraction
Cache vs Buffer
전통적으로 buffer 는 빠른 엔티티와 느린 엔티티 사이에서 중간 임시 저장소로 사용 됨
한 당사자가 다른 당사자(성능에 영향을 줌)를 기다려야 하므로, 작은 청크 보다는 전체 블럭의 데이터를 이동하는게 나음
데이터는 버퍼에 한번만 쓰고 한번만 읽음 (적어도 하나의 당사자에게는 가시적이다)
반면에, 캐시는 감춰져 있어서 당사자도 캐싱이 발생하는지 모른다.
또한 성능을 향상 시키고, 동일한 데이터를 여러번 빠르게 읽을 수 있다
자바 메소드에 추성화 캐싱을 적용해서, 실행 횟수를 줄인다
즉, 대상 메소드가 호출되면 주어진 아규먼트에 대해 이미 실행 되었는지 확인하는 캐싱 동작이 있다
캐시가 사용되면, 실제 메소드 실행 없이 캐시 결과가 응답된다.
메소드가 실행 되지 않았다면, 결과가 캐시되고 리턴된다.
캐시 추상화는 컨텐츠 업데이트, 제거 등의 기능으로 애플리케이션에서 변경 될수 있는 데이터를 처리하는데 유용함을 제공한다
Spring Framework 는 다른 서비스와 마찬가지로 추상화(not a cache implementation)를 제공하는 것이며, 실제 스토리지를 저장하는 캐시 스토어가 필요하다
몇가지 구현된(a few implementations) 추성화를 제공한다.
JDK java.util.concurrent.ConcurrentMap based caches, Ehcache 2.x, Gemfire cache, Caffeine, and JSR-107 compliant caches (such as Ehcache 3.x)
캐시 추상화는 멀티스레드 및 멀티프로세스에 대한 특별한 처리가 없다. 이러한 기능은 캐시 구현으로 처리 된다
캐시 추상화를 쓸려면 다음 두가지 측면을 고려해라
Caching declaration : 캐시 하는 방법 및 정책 식별
Cache configuration : 데이터가 저장되고 읽히는 backing cache
8.2. Declarative Annotation-based Caching
@Cacheable: Triggers cache population.
@CacheEvict: Triggers cache eviction.
@CachePut: 메소드 실행을 방해하지 않고, 캐시 업데이트 함.
@Caching: 메소드에 적용할 멀티플 캐시 조작을 재그룹 함.
@CacheConfig: 클래스 수준에서 캐시와 관련된 몇가지 일반적인 설정 공유.
8.2.1. The @Cacheable Annotation
@Cacheable 로 캐시 가능한 메소드를 정함
먼저 캐시를 할 name 을 정해야 함
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
findBook 메소드는 books 이름의 캐시와 연관 된다
메소드를 호출할 때 마다, 반복할 필요 없는지 캐시를 확인 한다
대부분의 경우, 하나의 캐시만 정의되지만, 멀티플 이름으로 정해서 둘 이상의 캐시를 사용하도록 할 수 있다
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
Default Key Generation
캐시는 본질적으로 key-value 스토어이며, 캐시된 메소드의 각 호출은, 캐시 접근에 적합한 key 로 변환해야 한다.
캐시 추상화는 다음의 알고리즘 기반으로 간단한 KeyGenerator 를 사용 함
파라미터가 없으면 SimpleKey.EMPTY
하나의 파라미터가 주어지면, 인스턴스 응답
둘 이상의 파라마티가 주어지면, 모든 파라미터를 포함하는 SimpleKey 응답
이 방법은 파라미터가 자연키이고, 유효한 hashCode() 와 equals() 메소드를 구현하면 적합하다.
그렇지 않다면, 다른 전략 필요
다른 key generator 를 제공하려면 org.springframework.cache.interceptor.KeyGenerator 인터페이스 구현 필요
default key generation 전략이 Spring 4.0 부터 바뀌었는데, 이전에는 equals() 를 안보고 hashCode() 만 고려 했음. 이거는 key 충돌이 발생할 수도 있기때문에, 새로운 SimpleKeyGenerator 는 복합키를 사용 함
Custom Key Generation Declaration
목표 메소드에 멀티플 아규먼트가 있고, 그중 몇개만 캐싱 기준으로 삼고 싶을때
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
boolean 아규먼트가 book 을 찾는데 영향을 주지만, 캐시에는 영향을 주지 않는다. 이럴 경우에는 어떻게?
@Cacheable 로 키 생성 방식을 지정할 수 있다. 또한 SpEL 사용 가능
다양한 SpEL 예제
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
* 커스텀 `keyGenerator` 적용 가능
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
> key 와 keyGenerator 파라미터는 서로 배타적이라 둘중에 하나만 해야 함. 그렇게 안하면 exceptioun 발생
##### Default Cache Resolution
* 캐시 추상화는 `CacheResolver` 로, 구성된 `CacheManager` 를 사용하여 캐시를 검색 함
* 다른 캐시 resolver 를 쓸려면, `org.springframework.cache.interceptor.CacheResolver` 를 구현해라
##### Custom Cache Resolution
* 기본 캐시 resolution 은 single `CacheManager` 에서 동작하고, 복잡하지 않는 요구사항에 적합 함
* 여러 캐시 매니저가 있을 경우, `cacheManager` 를 설정해서 아래처럼 조작 가능하다
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")
public Book findBook(ISBN isbn) {...}
* `CacheResolver` 도 대체 가능 함
@Cacheable(cacheResolver="runtimeCacheResolver")
public Book findBook(ISBN isbn) {...}
> Spring 4.1 부터 `value` 는 더이상 필수가 아님. `CacheResolver` 정보를 제공 가능하기 때문임
> cacheManager 와 cacheResolver 파라미터는 상호 배타적임. 둘중에 하나만 써야 함
##### Synchronized Caching
* 멀티 쓰레드 환경에서 동일한 파라미터로 동시에 호출 될 수 있다. 기본적으로 캐시 추상화는 lock 을 하지 않기 때문에 동일한 값이 여러번 계산될 수 있다
* 이렬 경우 `sync` 애트리뷰트로 lock 설정이 가능하다. 결국 하나의 쓰레드에서만 값을 계산하고, 다른 쓰레드에서는 차단 됨
@Cacheable(cacheNames="foos", sync=true)
public Foo executeExpensiveOperation(String id) {...}
##### Conditional Caching
* 때로는 주어진 아규먼트에 따라 캐싱이 결정날 수 도 있다.
* `condition` 파라미터로 SpEL 표현 가능 함.
* 다음 예제는 아구먼트 길이가 32보다 짧은 경우에만 캐시 함
@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name)
* `unless` 파라미터로 캐시 거부 가능하다. 하지만 `condition` 과는 다르게 `unless` 는 메소드 수행 이후에 평가 된다
* 다음의 예제는 paperback 일때에만 캐싱 하도록 확장 했다
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name)
* `java.util.Optional` 도 지원 가능하며 값이 주어진 경우에만 캐싱 함.
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional findBook(String name)
##### Available Caching SpEL Evaluation Context
* [문서](https://docs.spring.io/spring/docs/5.2.2.RELEASE/spring-framework-reference/integration.html#cache-spel-context) 참고
#### 8.2.2. The @CachePut Annotation
* 메소드 실행을 방해하지 않고, 캐시 업데이트 가능 `@CachePut`
* `@Cacheable` 와 동일한 옵션 제공
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
> 동일한 메소드에서 @CachePut and @Cacheable 둘다 사용하는건 권장하지 않음. 후자는 캐시를 사용하여 메소드 실행을 안하지만, 전자는 캐시 업데이트를 위해 실행 됨.
#### 8.2.3. The @CacheEvict annotation
* books 의 모든 캐시 제거
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)
* `beforeInvocation` 애트리뷰트로 메소드 실행 이전 or 이후(default) 에 캐시 제거 하도록 설정 가능 함.
#### 8.2.4. The @Caching Annotation
* 때로는 동일한 타입의 멀티플 애너테이션을 적용 해야 한다 (such as @CacheEvict or @CachePut)
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
#### 8.2.5. The @CacheConfig annotation
* 각 메소드마다 캐싱 설정 하기 번거로우니, 클래스 레벨에서 공통으로 캐싱 설정 적용 가능한 `@CacheConfig` 있음
* 캐시 name 을 지정 함
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
* operation-level customization (메소드인듯?) 는 `@CacheConfig` 보다 우선 함
#### 8.2.6. Enabling Caching Annotations
* `@EnableCaching` 붙히면 캐싱 자동 구성 됨
> 세부적으로 구성 하고 싶다면 `CachingConfigurer` 구현 하라. [javadoc](https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/cache/annotation/CachingConfigurer.html) 참고
> default 캐싱 애너테이션 advice 모드는 `proxy` 임
> 같은 클래스에서 Local 호출시에는 인터셉트가 안됨. compile-time or load-time 에 엮을려면 `aspectj` 모드 전환해라
* Cache annotation settings [참고](https://docs.spring.io/spring/docs/5.2.2.RELEASE/spring-framework-reference/integration.html#cache-annotation-enable)
> proxy 를 쓸려면 public 메소드에 캐시 애너테이션 쓸수 있다.
> Spring 은 인터페이스에 `@Cache*` 를 붙히지 말고, 클래스에 붙히길 권한다.
#### 8.2.7. Using Custom Annotations
* 필요할때 보자. 생략
### 8.3. JCache (JSR-107) Annotations
* 필요할때 보자. 생략
### 8.4. Declarative XML-based Caching
* 안봐도 됨
### 8.5. Configuring the Cache Storage
#### 8.5.1. JDK ConcurrentMap-based Cache
* ConcurrentHashMap 를 사용하는 Cache store
* 캐시는 애플리케이션에서 생성되기 때문에 기본적인 사용 예제, 테스트, 간단한 애플리케이션에 적합하다.
* 확장성이 좋고 매우 빠르지만, 관리 방법, persistence capabilities, eviction contracts 제공 안함
#### 8.5.2. Ehcache-based Cache
> Ehcache 3.x 은 JSR-107 와 완벽히 호환됨
* Ehcache 2.x implementation 은 `org.springframework.cache.ehcache` 패키지에 있음. 또한 `CacheManager` declare 필요
#### 8.5.3. Caffeine Cache
* Guava 캐시를 Java 8 로 재작성 한것임
* `CacheManager` 는 커스텀 Caffeine and CacheLoader 를 지원 함
* [caffeine 문서](https://github.com/ben-manes/caffeine/wiki) 참고
#### 8.5.4. GemFire-based Cache
* memory-oriented, disk-backed, elastically scalable, continuously available
#### 8.5.5. JSR-107 Cache
* JSR-107 호환 캐시 사용 가능
#### 8.5.6. Dealing with Caches without a Backing Store
* 실제 백업 캐시 없이 구성할 경우가 있는데, 에러를 방지하는 더미 캐시를 만듦
* 이럴 경우, 해당 메소드는 캐싱 되지 않고 계속 실행
* `CompositeCacheManager` 는 멀티플 `CacheManager` 인스턴스를 가졌는데, `fallbackToNoOpCache` flag 를 통해서, 비작동 캐시를 넣을 수 있음
### 8.6. Plugging-in Different Back-end Caches
* 배킹 스토어로 사용할 캐시 제품은 많다. CacheManager 가 없는경우 직접 구현 해야 한다.
8. Cache Abstraction
Spring Framework 3.1 이래로 캐싱 지원 했음
Spring Framework 4.1 부터 JSR-107 annotations 지원 및 커스터이징 옵션이 크게 확장 됨
8.1. Understanding the Cache Abstraction
Cache vs Buffer
전통적으로 buffer 는 빠른 엔티티와 느린 엔티티 사이에서 중간 임시 저장소로 사용 됨
한 당사자가 다른 당사자(성능에 영향을 줌)를 기다려야 하므로, 작은 청크 보다는 전체 블럭의 데이터를 이동하는게 나음
데이터는 버퍼에 한번만 쓰고 한번만 읽음 (적어도 하나의 당사자에게는 가시적이다)
반면에, 캐시는 감춰져 있어서 당사자도 캐싱이 발생하는지 모른다.
또한 성능을 향상 시키고, 동일한 데이터를 여러번 빠르게 읽을 수 있다
자바 메소드에 추성화 캐싱을 적용해서, 실행 횟수를 줄인다
캐시 추상화는 컨텐츠 업데이트, 제거 등의 기능으로 애플리케이션에서 변경 될수 있는 데이터를 처리하는데 유용함을 제공한다
Spring Framework 는 다른 서비스와 마찬가지로 추상화(not a cache implementation)를 제공하는 것이며, 실제 스토리지를 저장하는 캐시 스토어가 필요하다
몇가지 구현된(a few implementations) 추성화를 제공한다.
8.2. Declarative Annotation-based Caching
8.2.1. The @Cacheable Annotation
findBook
메소드는books
이름의 캐시와 연관 된다Default Key Generation
KeyGenerator
를 사용 함SimpleKey.EMPTY
SimpleKey
응답hashCode()
와equals()
메소드를 구현하면 적합하다.org.springframework.cache.interceptor.KeyGenerator
인터페이스 구현 필요Custom Key Generation Declaration
boolean
아규먼트가 book 을 찾는데 영향을 주지만, 캐시에는 영향을 주지 않는다. 이럴 경우에는 어떻게?@Cacheable
로 키 생성 방식을 지정할 수 있다. 또한 SpEL 사용 가능@Cacheable(cacheNames="books", key="#isbn.rawNumber") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") public Book findBook(ISBN isbn) {...}
@Cacheable(cacheResolver="runtimeCacheResolver") public Book findBook(ISBN isbn) {...}
@Cacheable(cacheNames="foos", sync=true) public Foo executeExpensiveOperation(String id) {...}
@Cacheable(cacheNames="book", condition="#name.length() < 32") public Book findBook(String name)
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") public Book findBook(String name)
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback") public Optional findBook(String name)
@CachePut(cacheNames="book", key="#isbn") public Book updateBook(ISBN isbn, BookDescriptor descriptor)
@CacheEvict(cacheNames="books", allEntries=true) public void loadBooks(InputStream batch)
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) public Book importBooks(String deposit, Date date)
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository {
}