====== Hibernate Cache ======
* [[https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch20.html|Chapter 20. Improving performance]]
* [[http://docs.jboss.org/jbossclustering/hibernate-caching/3.5/en-US/html/|JBossCache hibernate caching]] : JBossCache에 관한 문서이지만, Hibernate Cache에 관한 설명을 많이 포함하고 있음.
* [[https://github.com/kwon37xi/hibernate4-memcached|hibernate4-memcached]] - hibernate4 memcached 구현체.
* Entity클래스 변경에 대한 방어돼 있음.
* Memcached임에도 Region(namespace) 지원
* [[https://github.com/debop/hibernate-redis|hibernate-redis]] Hibernate4 redis 구현체. 현재 Entity 클래스 변경에 대한 방어가 안 돼 있는 것으로 보임.
===== Hibernate의 기본 캐시 전략 =====
* Memcached 등의 외부 서버보다는 로컬 JVM에서 값을 즉시 읽을 수 있는 [[java:ehcache|ehcache]]나 [[java:infinispan|Infinispan]] 계통을 사용할 것.
* [[http://infinispan.org/docs/6.0.x/user_guide/user_guide.html#_using_infinispan_as_jpa_hibernate_second_level_cache_provider|Infinispan 매뉴얼의 Hibernate 관련 절을 보면 Hibernate의 캐시 전략이 나옴]] -> 좀 더 자세히 읽어볼 것.
* 기본적으로 Replication-Invalidation 전략을 사용한다.
* [[java:ehcache|ehcache]] 사용시에
* Entity와 컬렉션은 ''replicatePuts=false'', ''replicateUpdatesViaCopy=false''를 통해 네트워크 부하를 낮춘다. 이는 또한 여러 서버의 순차 배포시에 Class 버전이 서로 안 맞는 상황에서 직렬화 오류나는 것을 방어해준다. 비동기로 한다.
* ''org.hibernate.cache.internal.StandardQueryCache''는 완전 local 캐시로만 적용한다.
* ''org.hibernate.cache.spi.UpdateTimestampsCache''는 ''replicatePuts=true'', ''replicateUpdatesViaCopy=true'' 및 비동기방식을 통해 모든 서버가 일관성있고 동일한 값을 유지하게 한다. 또한 서버가 뜰때 다른 서버의 ''UpdateTimestampsCache''들을 복제해 와서 사용해야 한다.
===== Cuncurrency Strategy =====
동시성 전략 4가지가 있는데 hibernate 문서 설명이 부족해서 로그를 찍어보며 확인해봄.
기본적으로 ''evict''와 ''evictAll''은 기본 흐름상에서 Hibernate에 의해 직접 호출되지는 않는다. 다른 메소드들에 의해 필요해 의해 호출된다. 이 두 메소드는 [[http://docs.oracle.com/javaee/6/api/javax/persistence/Cache.html|javax.persistence.Cache]]의 ''evict'', ''evictAll''메소드가 실행될 때 호출된다.
**테스트시에 쿼리 삭제, 수정 등을 하고서 flush후에 rollback 하면 어떻게 되는지 테스트**
==== READ_ONLY ====
읽기 전용. 우편 번호처럼 잘 안 변하는 것들에 적합할 듯. Read Only의 의미가 수정만 못하게 하는 것인지 수정과 삭제를 둘 다 못하게 하는 것인지에 대해서는 확실치 않다. 그래서 삭제는 가능한 것으로 가정하고 스토리를 짜면 아래 방식으로 했을 때 잘 작동한다.
삭제도 못하게 하려 한다면 ''lockItem''에서도 예외를 던져야 하는 것이 맞아 보인다.
''@org.hibernate.annotations.Immutable''로 지정된 엔티티와 컬렉션에 사용하는 것이 좋을 듯 하다.
* 읽기
* ''get'' : 캐시에서 읽어서 있으면 반환 한다.
* 최초 데이터 가져온 후 캐시 생성
* ''putFromLoad'' : minimalPutOverride == true일 경우에는 이미 캐시에 값이 존재하면 현재 것을 버린다. 아니면 항상 값을 캐시에 쓴다. 캐시에 썼으면 ''return true;'' 아니면 ''return false;''. ''putFromLoad''는 데이터는 가져왔으나 캐시가 없었을 때 호출되는 것인데, 그 사이에 다른 쓰레드에서 캐시에 데이터를 썼다면 굳이 다시 쓸 필요는 없다.
* 입력 : ''insert/afterInsert'' : 객체를 persist해도 호출하지 않음. 즉, persist시 캐싱 불가. ''return false;''
* 수정 - 예외를 발생시켜야 한다.
* ''lockItem'' : 객체 수정 전에 호출, READ_ONLY이므로 수정이 말이 안되니 ''return null;''. 여기서 예외를 던지면 삭제 기능이 작동할 수 없다.
* ''update'' : 객체 수정 쿼리 직후 호출. READ_ONLY인 상황에서 객체 수정은 오류이므로 ''UnsupportedOperationException''을 던진다.
* ''afterUpdate'' : ''update''에서 예외가 발생하지 않으면 호출된다. 그러나 위에서 이미 예외를 발생시켰으므로 원칙적으로는 호출돼서는 안된다. 하지만 아무튼 ''UnsupportedOperationException''을 던지는 것이 권고인 듯.
* ''unlockItem'' : ''update''에서 예외를 발생 시켰으면 호출된다. 따라서 이것은 항상 호출돼야 한다. 여기서 캐시를 ''evict''시킨다.
* 삭제시 :
* ''lockItem'' : 객체 삭제 전에 호출된다. 아무것도 하지 않고 ''return null;''
* ''remove'' : 삭제 쿼리 후 실행된다. 여기서 ''evict''를 호출하여 캐시를 삭제한다.
* ''unlockItem'' : ''remove'' 후에 호출된다. 아무것도 하지 않는다.
* 삭제 쿼리
* ''lockRegion'' : 쿼리 실행 전에 호출된다. 아무것도 하지 않고 ''retur null;''
* ''removeAll'' : 쿼리 실행 전에 호출된다. 아무것도 하지 않는다. 실제 쿼리가 날라가기 전이므로 이 시기에 캐시를 삭제하면 쿼리 결과가 Rollback시에 불필요한 부하만 증가할 것으로 보인다.
* ''unlockRegion'' : 쿼리 실행 후에 호출된다. 여기서 ''evictAll''을 호출한다. ''evictAll''에서 실질적인 eviction을 수행한다.
==== NONSTRICT_READ_WRITE ====
엄격하지 않은 읽기/쓰기. 객체 동시 수정 등에 대한 고려를 하지 않고 캐싱을 한다. 이 방식은 **하나의 객체가 동시에 수정될 가능성이 거의 없을 때** 사용한다.
동시 수정이 적은 데이터에 대한 분산 캐시에서는 이것이 적합해 보인다.
* 읽기
* ''get'' : 캐시에서 읽어서 있으면 반환 한다.
* 최초 데이터 가져온 후 캐시 생성
* ''putFromLoad'' : minimalPutOverride == true일 경우에는 이미 캐시에 값이 존재하면 현재 것을 버린다. 아니면 항상 값을 캐시에 쓴다. 캐시에 썼으면 ''return true;'' 아니면 ''return false;''
* 입력 : ''insert/afterInsert'' : 객체를 persist해도 호출하지 않음. 즉, persist시 캐싱 불가. ''return false;''
* 수정
* ''lockItem'' : 객체 수정 전에 호출, 캐시를 삭제할 것이기 때문에 Lock 불필요. ''return null;''
* ''update'' : 객체 수정 쿼리 직후 호출. 아무것도 하지 않고 수정이 없었다는 의미로 ''return false;''.
* ''afterUpdate'' : ''update'' 호출 직후 호출되며 여기서 캐시를 수정하지 말고 즉시 삭제(evict)해 버릴 것. 그리고 수정이 없었다는 의미로 ''return false;''
* ''unlockItem'' : 호출 안됨.
* 삭제
* ''lockItem'' : 객체 삭제 전에 호출된다. 아무것도 하지 않고 ''return null;''
* ''remove'' : 삭제 쿼리 후 실행된다. 여기서 ''evict''를 호출하여 캐시를 삭제한다.
* ''unlockItem'' : ''remove'' 후에 호출된다. 아무것도 하지 않는다.
* 수정/삭제 쿼리
* ''lockRegion'' : 쿼리 실행 전에 호출된다. 아무것도 하지 않고 ''retur null;''
* ''removeAll'' : 쿼리 실행 전에 호출된다. 아무것도 하지 않는다. 실제 쿼리가 날라가기 전이므로 이 시기에 캐시를 삭제하면 쿼리 결과가 Rollback시에 불필요한 부하만 증가할 것으로 보인다.
* ''unlockRegion'' : 쿼리 실행 후에 호출된다. 여기서 ''evictAll''을 호출한다. ''evictAll''에서 실질적인 eviction을 수행한다.
==== READ_WRITE ====
엄격한 읽기/쓰기로 두개 이상의 쓰레드에서 동시 수정할 가능성에 대해 고려하고 만들어야 한다.
READ_WRITE + 클러스터링 캐시 구현체를 사용할 경우 **캐시 구현체는 Lock 기능을 제공**해야만 한다. 객체 생성, 수정시에 캐시 서버에 대한 접근이 매우 빈번하여 분산 캐시에서는 성능이 떨어질 수 있다. 아래는 Memcached기준으로 생각해본 구현전략.
* 읽기
* ''get'' : 캐시에서 읽어서 있으면 반환 한다. txTimestamp로 작업필요할 듯.
* 최초 데이터 가져온 후 캐시 생성
* ''putFromLoad'' : 캐시에서 값을 읽어 존재하지 않으면 무조건 쓰고, 이미 캐시가 존재하며 현재 가진 것이 최신이면 덮어쓰고, 이미 있는 캐시가 더 최신 데이터이면 현재 것을 버린다.
* 입력
* ''lockItem''
* ''insert''
* ''afterInsert'' : 보통은 이것만 구현해도 될 듯. 객체를 캐시에서 가져와보고 이미 캐시가 존재하면 DB insert 직후 누군가가 이미 읽어가서 ''putFromLoad''로 인해 캐시가 생성 됐다는 뜻이므로 cache에 다시 저장하지 말고 빠져나옴.
* ''unlockItem''
* 수정
* ''lockItem''
* ''update''
* ''afterUpdate'' : 보통은 이것만 구현해도 될 듯. 객체를 cache에서 get해보고 객체 version등을 체크해서 현재 가진것보다 최신이면 수정하지 말고, 현재가 더 최신이면 캐시 갱신
* ''unlockItem''
* 삭제
* 살펴보자...
==== TRANSACTIONAL ====
JTA에서만 사용한다.
===== Hibernate Cache 활성화 =====
Map props = new HashMap();
// hibernate.cache.use_second_level_cache
props.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, true);
// hibernate.cache.use_query_cache
props.put(AvailableSettings.USE_QUERY_CACHE, true);
// hibernate.cache.region.factory_class - 캐시 구현체 지정
props.put(AvailableSettings.CACHE_REGION_FACTORY, CachingRegionFactory.class.getName());
// hibernate.cache.region_prefix
props.put(AvailableSettings.CACHE_REGION_PREFIX, "cachetest");
// hibernate.cache.default_cache_concurrency_strategy
props.put(AvailableSettings.DEFAULT_CACHE_CONCURRENCY_STRATEGY, CacheConcurrencyStrategy.READ_WRITE);
// ...
EntityManagerFactory emf = Persistence.createEntityManagerFactory("cachetest", props);
* Hibernate는 ''@Cache'' 애노테이션이 있으면 캐시를 무조건 수행했다. 즉, 이런 상황에서 캐시를 명시적으로 끄려면 ''hibernate.cache.use_second_level_cache=false''를 명시적으로 지정해야만 한다.
===== JPA =====
* JPA 캐시의 경우 원칙적으로 ''ENABLE_SELECTIVE'' 등의 ''shared-cache-mode'' 설정이 필요하지만, Hibernate Cache Annotaion과 Hint를 사용할 때는 이 값을 완전히 무시하는 것으로 보인다.
* [[http://docs.oracle.com/javaee/6/api/javax/persistence/SharedCacheMode.html|SharedCacheMode]]
* ''ENABLE_SELECTIVE'' : 명시적으로 ''@Cacheable(true)'' 인 엔티티만 캐시
* ''DISABLE_SELECTIVE'' : 모든 엔티티를 캐시하되 ''@Cacheable(false)'' 제외
* ''NONE'' : 캐시 안함
* 해당 값은 ''javax.persistence.sharedCache.mode'' 프라퍼티로 설정해도 된다.
===== @Cache =====
@Cache(
CacheConcurrencyStrategy usage(); (1)
String region() default ""; (2)
String include() default "all"; (3)
)
* ''usage'' : ''NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL''
* ''region'' : Region. 안적으면 FQCN 기준으로 자동 생성
* ''include''
* ''all'' : 모든 필드
* ''non-lazy'' : non-lazy인 필드들만
* Hibernate는 JPA의 ''@Cacheable'' 애노테이션을 사실상 무시했고, ''@Cache'' 애노테이션만으로 작동하였다.
===== Entity Cache =====
* Entity는 update 쿼리 실행시 region의 expire가 발생한다.
@Entity
@Table(name = "books")
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "books")
public class Book implements Serializable {
....
}
===== Entity Collection Cache =====
==== 기본 ====
엔티티의 컬렉션을 캐시하려면 해당 컬렉션에 ''@Cache'' 애노테이션을 붙여야 한다.
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public SortedSet getTickets() {
return tickets;
}
양방향 관계의 경우 **명시적으로 Collection 쪽에 수정이 발생**해야만 캐시가 갱신된다.
==== OneToOne not owning size ====
* OneToOne 관계의 not owning side(mappedBy를 명시한 부모측)에는 캐시가 걸리지 않는 것으로 보인다.[[https://forum.hibernate.org/viewtopic.php?f=1&t=976936|참조]]
===== Query Cache =====
* Query Cache는 기본적으로 region을 지정하지 않으면 ''org.hibernate.cache.internal.StandardQueryCache'' region에 캐시 결과를 저장한다.
* query는 명시적 expire는 없고, entity가 수정될 경우 해당 entity의 update time stamp를 기반으로 쿼리를 다시 날릴지 아니면 캐시된 데이터를 사용할지 결정한다.
* query는 ''org.hibernate.cache.spi.UpdateTimestampsCache'' region을 통해 특정 Entity의 timestamp를 저장한다.
* UpdateTimestampCache에 의해 query의 자동 expire가 이루어 지므로 굳이 Query에 대한 region을 따로 지정하지 않아도 된다.
* ''setHint''를 통해 캐시하도록 지정
TypedQuery query = em.createNamedQuery("Book.byEdition", Book.class);
query.setParameter("edition", 3);
query.setHint("org.hibernate.cacheable", true);
query.setHint("org.hibernate.cacheMode", CacheMode.NORMAL);
query.setHint("org.hibernate.cacheRegion", "book-by-edition"); // region 지정
* ''NamedQuery에 지정''
@Entity
@Table(name = "books")
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "books")
@NamedQuery(name = "Book.byEdition",
query = "from Book where edition=:edition",
hints = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "book-by-edition")
}
)
public class Book implements Serializable {
....
}
===== Spring Data JPA Query Cache =====
public interface UserRepository extends Repository {
@QueryHints(value = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "user-by-lastname")
})
Page findByLastname(String lastname, Pageable pageable);
}
===== Evict All =====
현재 Hibernate는 모든 리젼(Region)을 ''evict''하려면 다음을 호출해야 한다.
org.hibernate.Cache cache = sessionFactory.getCache();
cache.evictEntityRegions();
cache.evictQueryRegions();
cache.evictDefaultQueryRegion();
cache.evictCollectionRegions();
===== Cache 관련 Properties =====
* ''org.hibernate.cacheable'' Whether or not a query is cacheable ( eg. new Boolean(true) ), defaults to false
* ''org.hibernate.cacheMode'' Override the cache mode for this query ( eg. CacheMode.REFRESH ) - [[https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/CacheMode.html|CacheMode]]
* ''org.hibernate.cacheRegion'' Cache region of this query ( eg. new String("regionName") )
===== Query 실행 후 Entity Cache 갱신 문제 =====
* PK 기반 쿼리는 Cache에서 (있으면) 값을 가져오고, 쿼리 실행 후 필요하면 Cache에 값을 설정한다.
* 하지만 Query 실행시에는 (Query Cache 가 꺼져 있으면) 쿼리 결과 Entity들을 다시 Cache에 넣는 일을 한다(''putFromLoad'').
* ''minimalPutOverride=true''가 이를 방지하는 역할을 한다. 캐시 구현체가 ''minimalPutOverride''를 구현하지 않았을 수도 있다.
* 하지만 ''minimalPutOverride''를 켜지 않았을 경우에는 ''org.hibernate.cacheMode=CacheMode.GET'' 혹은 그 외 값으로 통해 해당 쿼리에 대한 캐시 사용을 꺼버린다.
*
===== 참조문서 =====
* [[http://www.javalobby.org/java/forums/t48846.html|Hibernate: Truly Understanding the Second-Level and Query Caches]]
* [[http://java.dzone.com/articles/all-about-hibernate-second|All About Hibernate Second Level Cache | Javalobby]]
* [[http://darren.oldag.net/2008/11/hibernate-query-cache-dirty-little_04.html|capricious diatribes: Hibernate Query Cache: A Dirty Little Secret]]
* [[http://apmblog.compuware.com/2009/03/24/understanding-caching-in-hibernate-part-three-the-second-level-cache/|Understanding Caching in Hibernate – Part Three : The Second Level Cache]]
* [[http://www.devx.com/dbzone/Article/29685|Speed Up Your Hibernate Applications with Second-Level Caching]]
* [[http://www.javabeat.net/introduction-to-hibernate-caching/|What is Hibernate Caching?]] : 동시성 전략에 대해 자세히 설명
* [[http://learningviacode.blogspot.in/2013/08/cachemodes-in-hibernate.html|Learning the code way: CacheModes in Hibernate]]
* [[http://blogs.innovationm.com/spring-hibernate-with-ehcache/|Spring Hibernate With EhCache | InnovationM Blog]]
* [[https://www.baeldung.com/hibernate-second-level-cache|Hibernate Second Level Cache]]