====== 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]]