Hibernate는 1차 캐시를 제공한다. 1차 캐시는 영속성 컨텍스트에 존재한다. 따라서 영속성 컨텍스트와 생명주기가 동일하다. 즉, 트랜잭션이 종료되면 1차 캐시도 종료된다. 서로 다른 isolation에서의 concurrent를 보장하고자 하는 요구가 생긴다면 2차 캐시의 사용을 고려해볼 수 있다. 2차 캐시는 SessionFactory-Scoped 이기 때문에 같은 session factory에서 생성된 세션들은 모두 2차 캐시를 공유할 수 있다.
1차 캐시는 Transaction 범위 내에서 동작하고, 2차 캐시는 Application 범위 내에서 동작한다.SessionFactory ?
데이터베이스에 도메인 모델을 맵핑하는 thread-safe한 ( 그리고 불변의) 표현이다.
org.hibernate.Session 인스턴스의 팩토리로 동작한다.
JPA의 EntityManagerFactory에 해당하는 명세이며 기본적으로 이 두 가지는 동일한 SessionFactory 구현으로 수렴됩니다. (위 다이어그램을 보면, EntityManagerFactory와 SessionFactory는 인터페이스이고 SessionFactoryImpl이 구현체이다. 두 인터페이스를 SessionFactoryImpl가 구현한다는 뜻) SessionFactory 하나를 생성하기 위해서는 매우 비싼값을 치러야한다(시스템 자원을 많이 소모한다). 그래서, 주어진 어떤 데이터베이스에 대해 애플리케이션은 오직 하나의 연관된 SassionFactor를 가져야한다. SessionFactory는 2단계 캐시, 커넥션 풀, 트랜잭션 시스템 통합 등과 같이 Hibernate가 모든 세션에서 사용하는 서비스를 유지 관리한다. https://docs.jboss.org/hibernate/orm/5.5/userguide/html_single/Hibernate_User_Guide.html#architecture
1차 캐시에 instance가 있는 경우 1차 캐시에서 반환1차 캐시에 instance가 없지만 2차 캐시에는 존재하는 경우 2차 캐시에서 반환instance가 영속성 컨텍스트(1차 캐시)에 있으면 같은 session을 공유하는 작업들은 session이 종료될 때까지 1차 캐시 해당하는 데이터를 가져오게 되고, 영속성 컨텍스트가 종료되면 해당 instance도 같이 사라진다.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.2.2.Final</version>
</dependency>
hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
region factory는 cache provider와 hibernate 사이를 이어주는 역할을 한다. 여기서는 cache provider로 가장 많이 사용하는 EhCacheRegionFactory를 사용했다.@Cache의 속성인 usage의 값으로 CacheConcurrencyStrategy를 설정해서 캐시의 동시성 전략을 설정할 수 있다.
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface Cache {
/**
* The concurrency strategy chosen.
*/
CacheConcurrencyStrategy usage();
/**
* The cache region name.
*/
String region() default "";
/**
* How lazy properties are included in the second level cache. Default value is "all"; other allowable
* value: "non-lazy"
*/
String include() default "all";
}
hibernate는 다음과 같은 3가지 종류의 캐시를 지원한다.
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "foo")
public class Foo {
...
}
@Cacheable는 엔티티 캐시 적용시 사용하는 어노테이션이다. (hibernate 에서는 없어도 @Cache만으로 동작한다. mark 용으로 사용하는 것 같다..)@Cache는 하이버네이트 전용으로 캐시와 관련돼서 더 세밀한 설정이 필요할 때 사용한다.EhCache API를 사용해서 다음과 같이 캐시에 바로 접근할 수도 있다.
Foo foo = new Foo();
fooService.create(foo);
fooService.findOne(foo.getId());
int size = CacheManager.ALL_CACHE_MANAGERS.get(0)
.getCache("com.baeldung.hibernate.cache.model.Foo").getSize();
assertThat(size, greaterThan(0));
com.baeldung.hibernate.cache.model.Foo는 Foo가 들어가는 region의 이름을 말한다.@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@OneToMany
private Collection<Bar> bars;
}
@Cache 어노테이션을 붙여줘야 한다.HQL(Hibernate Query Lang)의 결과를 캐싱할 수도 있다. 변경이 드문 엔티티들에 사용하면 유용하다.
쿼리 캐시를 사용하기 위해선 hibernate.cache.use_query_cache의 값을 true로 설정해줘야 한다.
hibernate.cache.use_query_cache=true
그리고 사용하는 쿼리에 queryHint 를 통해 캐시를 사용함을 명시해줘야한다.
entityManager.createQuery("select f from Foo f")
.setHint("org.hibernate.cacheable", true)
.getResultList();
Spring Data JPA를 사용하면 다음과 같다.
public interface UserRepository extends Repository<Foo, Long> {
@QueryHints(value = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "user-by-lastname") // cache-region 값 설정
})
Page<User> findByLastname(String lastname, Pageable pageable);
}