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);
}