환영합니다. Hibernate 두 번째 레벨 캐시 예제 튜토리얼에 오신 것을 환영합니다. 오늘은 Hibernate EHCache에 대해 살펴보겠습니다. 이는 가장 인기 있는 Hibernate 두 번째 레벨 캐시 제공자입니다.
Hibernate Second Level Cache
대규모 응용 프로그램에서 Hibernate를 사용하는 주요 이점 중 하나는 캐시 지원으로 데이터베이스 쿼리를 줄이고 성능을 향상시킵니다. 이전 예제에서는 Hibernate First Level Cache를 살펴보았고 오늘은 Hibernate EHCache를 사용한 Hibernate Second Level Cache를 살펴보겠습니다. Hibernate Second Level 캐시 제공자에는 EHCache와 Infinispan이 포함되어 있지만 EHCache가 더 인기가 있으며 이를 예제 프로젝트에 사용할 것입니다. 그러나 프로젝트로 이동하기 전에 객체를 캐싱하는 다양한 전략을 알아야 합니다.
- Read Only:이 캐싱 전략은 항상 읽지만 업데이트되지 않을 영속 객체에 사용해야 합니다. 데이터베이스에서 객체가 업데이트되었는지 확인할 필요가 없는 응용 프로그램 구성 및 기타 정적 데이터를 읽고 캐싱하는 데 적합합니다. 데이터베이스에서 객체가 업데이트되었는지 확인하는 오버로드가 없기 때문에 최상의 성능을 제공하는 가장 간단한 전략입니다.
- 읽기 쓰기: 하이버네이트 응용 프로그램에서 업데이트할 수 있는 지속적인 객체에 좋습니다. 그러나 데이터가 백엔드 또는 다른 응용 프로그램을 통해 업데이트되는 경우 하이버네이트가 이를 알 수 있는 방법이 없으며 데이터가 오래될 수 있습니다. 따라서이 전략을 사용하는 동안 Hibernate API를 사용하여 데이터를 업데이트하는지 확인하십시오.
- 비제한 읽기 쓰기: 응용 프로그램이 데이터를 가끔 업데이트해야하고 엄격한 트랜잭션 격리가 필요하지 않은 경우 비제한 읽기-쓰기 캐시가 적절할 수 있습니다.
- 트랜잭션: 트랜잭션 캐시 전략은 JBoss TreeCache와 같은 완전 트랜잭션 캐시 제공자를 지원합니다. 이러한 캐시는 JTA 환경에서만 사용할 수 있으며 hibernate.transaction.manager_lookup_class를 지정해야합니다.
하이버네이트 EHCache
EHCache가 모든 위의 캐시 전략을 지원하기 때문에 하이버네이트에서 두 번째 수준 캐시를 찾을 때 최상의 선택입니다. EHCache에 대해 자세히 다루지는 않겠지만 주요 초점은 하이버네이트 응용 프로그램에서 작동하도록하는 것입니다. Eclipse 또는 선호하는 IDE에서 메이븐 프로젝트를 만들고 최종 구현은 아래 이미지와 같이 보입니다. 이제 응용 프로그램의 각 구성 요소를 하나씩 살펴 보겠습니다.
Hibernate EHCache Maven 종속성
하이버네이트 두 번째 레벨 캐시를 사용하려면 응용 프로그램에 ehcache-core와 hibernate-ehcache 종속성을 추가해야합니다. EHCache는 로깅을 위해 slf4j를 사용하므로 로깅 목적으로 slf4j-simple도 추가했습니다. 이 모든 API의 최신 버전을 사용하고 있으며, hibernate-ehcache API가 ehcache-core API와 호환되지 않을 가능성이 약간 있으므로 그 경우에는 hibernate-ehcache의 pom.xml을 확인하여 사용할 올바른 버전을 찾아야합니다. 최종 pom.xml은 다음과 같습니다.
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.hibernate</groupId>
<artifactId>HibernateEHCacheExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>Hibernate Secondary Level Cache Example using EHCache implementation</description>
<dependencies>
<!-- Hibernate Core API -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.5.Final</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<!-- EHCache Core APIs -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.9</version>
</dependency>
<!-- Hibernate EHCache API -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>4.3.5.Final</version>
</dependency>
<!-- EHCache uses slf4j for logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</project>
Hibernate 두 번째 레벨 캐시 – Hibernate EHCache 구성
하이버네이트 두 번째 레벨 캐시는 기본적으로 비활성화되어 있으므로 활성화하고 작동하도록 몇 가지 구성을 추가해야합니다. 우리의 hibernate.cfg.xml 파일은 아래와 같습니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration SYSTEM "classpath://org/hibernate/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.password">pankaj123</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
<property name="hibernate.connection.username">pankaj</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- For singleton factory -->
<!-- <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>
-->
<!-- enable second level cache and query cache -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="net.sf.ehcache.configurationResourceName">/myehcache.xml</property>
<mapping class="com.journaldev.hibernate.model.Employee" />
<mapping class="com.journaldev.hibernate.model.Address" />
</session-factory>
</hibernate-configuration>
하이버네이트 두 번째 레벨 캐시 구성에 관한 몇 가지 중요한 점은:
- hibernate.cache.region.factory_class는 두 번째 레벨 캐싱의 팩토리 클래스를 정의하는 데 사용됩니다. 나는 이를 위해
org.hibernate.cache.ehcache.EhCacheRegionFactory
를 사용하고 있습니다. 팩토리 클래스를 싱글톤으로 만들고 싶다면org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
클래스를 사용해야 합니다. Hibernate 3을 사용하는 경우에는 해당 클래스가net.sf.ehcache.hibernate.EhCacheRegionFactory
및net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory
로 대응됩니다. - hibernate.cache.use_second_level_cache는 두 번째 레벨 캐시를 활성화하는 데 사용됩니다.
- hibernate.cache.use_query_cache는 쿼리 캐시를 활성화하는 데 사용됩니다. 이 기능이 없으면 HQL 쿼리 결과가 캐시되지 않습니다.
- net.sf.ehcache.configurationResourceName은 EHCache 구성 파일의 위치를 정의하는 데 사용됩니다. 이는 선택적인 매개변수이며, 지정되지 않은 경우 EHCache는 응용 프로그램 클래스 경로에 있는 ehcache.xml 파일을 찾으려고 합니다.
Hibernate EHCache 구성 파일
저희의 EHCache 구성 파일인 myehcache.xml은 아래와 같습니다.
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
monitoring="autodetect" dynamicConfig="true">
<diskStore path="java.io.tmpdir/ehcache" />
<defaultCache maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30"
maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU" statistics="true">
<persistence strategy="localTempSwap" />
</defaultCache>
<cache name="employee" maxEntriesLocalHeap="10000" eternal="false"
timeToIdleSeconds="5" timeToLiveSeconds="10">
<persistence strategy="localTempSwap" />
</cache>
<cache name="org.hibernate.cache.internal.StandardQueryCache"
maxEntriesLocalHeap="5" eternal="false" timeToLiveSeconds="120">
<persistence strategy="localTempSwap" />
</cache>
<cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
maxEntriesLocalHeap="5000" eternal="true">
<persistence strategy="localTempSwap" />
</cache>
</ehcache>
Hibernate EHCache는 많은 옵션을 제공하지만, 중요한 몇 가지 구성은 위에서 설명한 것입니다.
- diskStore: EHCache는 데이터를 메모리에 저장하지만 오버플로우가 발생할 때 파일 시스템에 데이터를 쓰기 시작합니다. 이 속성을 사용하여 EHCache가 오버플로된 데이터를 쓸 위치를 정의합니다.
- defaultCache: 이것은 필수 구성 요소이며 개체를 캐시해야하고 그에 대한 캐싱 영역이 정의되지 않은 경우 사용됩니다.
- 캐시 이름=”직원”: 캐시 요소를 사용하여 영역 및 구성을 정의합니다. 여러 영역과 그 속성을 정의 할 수 있으며, 모델 빈 캐시 속성을 정의하는 동안 캐싱 전략으로 영역을 정의 할 수도 있습니다. 캐시 속성은 이름으로 쉽게 이해하고 명확합니다.
- 캐시 영역
org.hibernate.cache.internal.StandardQueryCache
및org.hibernate.cache.spi.UpdateTimestampsCache
가 정의되었으므로 EHCache가 경고를 발생시켰습니다.
Hibernate 두 번째 수준 캐시 – 모델 빈 캐싱 전략
org.hibernate.annotations.Cache
주석을 사용하여 캐싱 구성을 제공합니다. org.hibernate.annotations.CacheConcurrencyStrategy
는 캐싱 전략을 정의하는 데 사용되며 모델 빈에 대해 사용할 캐시 영역을 정의 할 수도 있습니다.
package com.journaldev.hibernate.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
@Entity
@Table(name = "ADDRESS")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="employee")
public class Address {
@Id
@Column(name = "emp_id", unique = true, nullable = false)
@GeneratedValue(generator = "gen")
@GenericGenerator(name = "gen", strategy = "foreign",
parameters = { @Parameter(name = "property", value = "employee") })
private long id;
@Column(name = "address_line1")
private String addressLine1;
@Column(name = "zipcode")
private String zipcode;
@Column(name = "city")
private String city;
@OneToOne
@PrimaryKeyJoinColumn
private Employee employee;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
}
package com.journaldev.hibernate.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Cascade;
@Entity
@Table(name = "EMPLOYEE")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "emp_id")
private long id;
@Column(name = "emp_name")
private String name;
@Column(name = "emp_salary")
private double salary;
@OneToOne(mappedBy = "employee")
@Cascade(value = org.hibernate.annotations.CascadeType.ALL)
private Address address;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
참고하십시오. HQL 예제와 동일한 데이터베이스 설정을 사용하고 있습니다. 데이터베이스 테이블을 만들고 샘플 데이터를 로드하려면 해당 사항을 확인하십시오.
Hibernate SessionFactory 유틸리티 클래스
우리는 하이버네이트를 구성하고 SessionFactory
싱글톤 인스턴스를 가져오기 위한 간단한 유틸리티 클래스를 갖고 있습니다.
package com.journaldev.hibernate.util;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
public class HibernateUtil {
private static SessionFactory sessionFactory;
private static SessionFactory buildSessionFactory() {
try {
// 하이버네이트.cfg.xml에서 SessionFactory 생성
Configuration configuration = new Configuration();
configuration.configure("hibernate.cfg.xml");
System.out.println("Hibernate Configuration loaded");
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
System.out.println("Hibernate serviceRegistry created");
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
return sessionFactory;
}
catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
ex.printStackTrace();
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
if(sessionFactory == null) sessionFactory = buildSessionFactory();
return sessionFactory;
}
}
하이버네이트 EHCache를 사용하는 하이버네이트 두 번째 수준 캐시 프로젝트가 준비되었습니다. 이를 테스트하기 위해 간단한 프로그램을 작성해 봅시다.
하이버네이트 EHCache 테스트 프로그램
package com.journaldev.hibernate.main;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.stat.Statistics;
import com.journaldev.hibernate.model.Employee;
import com.journaldev.hibernate.util.HibernateUtil;
public class HibernateEHCacheMain {
public static void main(String[] args) {
System.out.println("Temp Dir:"+System.getProperty("java.io.tmpdir"));
// 세션 초기화
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Statistics stats = sessionFactory.getStatistics();
System.out.println("Stats enabled="+stats.isStatisticsEnabled());
stats.setStatisticsEnabled(true);
System.out.println("Stats enabled="+stats.isStatisticsEnabled());
Session session = sessionFactory.openSession();
Session otherSession = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Transaction otherTransaction = otherSession.beginTransaction();
printStats(stats, 0);
Employee emp = (Employee) session.load(Employee.class, 1L);
printData(emp, stats, 1);
emp = (Employee) session.load(Employee.class, 1L);
printData(emp, stats, 2);
// 첫 번째 수준 캐시를 지우고 두 번째 수준 캐시를 사용합니다
session.evict(emp);
emp = (Employee) session.load(Employee.class, 1L);
printData(emp, stats, 3);
emp = (Employee) session.load(Employee.class, 3L);
printData(emp, stats, 4);
emp = (Employee) otherSession.load(Employee.class, 1L);
printData(emp, stats, 5);
// 리소스 해제
transaction.commit();
otherTransaction.commit();
sessionFactory.close();
}
private static void printStats(Statistics stats, int i) {
System.out.println("***** " + i + " *****");
System.out.println("Fetch Count="
+ stats.getEntityFetchCount());
System.out.println("Second Level Hit Count="
+ stats.getSecondLevelCacheHitCount());
System.out
.println("Second Level Miss Count="
+ stats
.getSecondLevelCacheMissCount());
System.out.println("Second Level Put Count="
+ stats.getSecondLevelCachePutCount());
}
private static void printData(Employee emp, Statistics stats, int count) {
System.out.println(count+":: Name="+emp.getName()+", Zipcode="+emp.getAddress().getZipcode());
printStats(stats, count);
}
}
org.hibernate.stat.Statistics
는 Hibernate SessionFactory의 통계를 제공하며, 페치 횟수 및 두 번째 레벨 캐시의 히트, 미스 및 입력 횟수를 출력하는 데 사용됩니다. 통계는 성능 향상을 위해 기본적으로 비활성화되어 있으며, 이것이 프로그램 시작 시에 활성화하는 이유입니다. 위의 프로그램을 실행하면 Hibernate 및 EHCache API에서 생성된 많은 출력이 나오지만, 우리가 인쇄하는 데이터에만 관심이 있습니다. 샘플 실행은 다음 출력을 출력합니다.
Temp Dir:/var/folders/h4/q73jjy0902g51wkw0w69c0600000gn/T/
Hibernate Configuration loaded
Hibernate serviceRegistry created
Stats enabled=false
Stats enabled=true
***** 0 *****
Fetch Count=0
Second Level Hit Count=0
Second Level Miss Count=0
Second Level Put Count=0
Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
1:: Name=Pankaj, Zipcode=95129
***** 1 *****
Fetch Count=1
Second Level Hit Count=0
Second Level Miss Count=1
Second Level Put Count=2
2:: Name=Pankaj, Zipcode=95129
***** 2 *****
Fetch Count=1
Second Level Hit Count=0
Second Level Miss Count=1
Second Level Put Count=2
3:: Name=Pankaj, Zipcode=95129
***** 3 *****
Fetch Count=1
Second Level Hit Count=2
Second Level Miss Count=1
Second Level Put Count=2
Hibernate: select employee0_.emp_id as emp_id1_1_0_, employee0_.emp_name as emp_name2_1_0_, employee0_.emp_salary as emp_sala3_1_0_, address1_.emp_id as emp_id1_0_1_, address1_.address_line1 as address_2_0_1_, address1_.city as city3_0_1_, address1_.zipcode as zipcode4_0_1_ from EMPLOYEE employee0_ left outer join ADDRESS address1_ on employee0_.emp_id=address1_.emp_id where employee0_.emp_id=?
4:: Name=Lisa, Zipcode=560100
***** 4 *****
Fetch Count=2
Second Level Hit Count=2
Second Level Miss Count=2
Second Level Put Count=4
5:: Name=Pankaj, Zipcode=95129
***** 5 *****
Fetch Count=2
Second Level Hit Count=4
Second Level Miss Count=2
Second Level Put Count=4
출력에서 볼 수 있듯이, 통계는 처음에 비활성화되었지만 하이버네이트 두 번째 레벨 캐시를 확인하기 위해 활성화했습니다. 출력의 단계별 설명은 다음과 같습니다:
- 우리의 애플리케이션에 데이터를로드하기 전에 예상대로 모든 통계는 0입니다.
- 우리가 id=1인 직원을 처음으로로드 할 때, 먼저 첫 번째 레벨 캐시에서 검색 한 다음 두 번째 레벨 캐시로 이동합니다. 캐시에서 찾을 수 없으면 데이터베이스 쿼리가 실행되어 페치 횟수가 1이됩니다. 객체가로드되면 첫 번째 레벨 캐시와 두 번째 레벨 캐시 모두에 저장됩니다. 따라서 2차 레벨 히트 카운트는 0이 유지되고 미스 카운트는 1이 됩니다. put 카운트가 2임을 유의하십시오. 이는 직원 객체가 주소를 포함하기 때문에 두 개의 객체가 모두 두 번째 레벨 캐시에 저장되고 카운트가 2로 증가합니다.
- 다음으로, 우리는 다시 id=1인 직원을로드합니다. 이번에는 첫 번째 레벨 캐시에 있습니다. 따라서 데이터베이스 쿼리가 표시되지 않으며 다른 모든 2차 레벨 캐시 통계도 동일합니다.
- 다음은
evict()
메서드를 사용하여 직원 객체를 첫 번째 레벨 캐시에서 제거합니다. 이제 로드를 시도할 때 하이버네이트가 두 번째 레벨 캐시에서 찾습니다. 이것이 데이터베이스 쿼리가 발생하지 않고 피치 카운트가 1로 유지되는 이유입니다. 히트 카운트가 0에서 2로 증가하는 것에 유의하십시오. 직원 및 주소 객체가 두 번째 레벨 캐시에서 읽혔기 때문입니다. 두 번째 레벨 미스 및 풋 카운트는 이전 값으로 유지됩니다. - 다음으로 id=3인 직원을 로드하고 데이터베이스 쿼리가 실행되어 피치 카운트가 2로 증가하고 미스 카운트가 1에서 2로 증가하며 풋 카운트가 2에서 4로 증가합니다.
- 다음으로 다른 세션에서 id=1인 직원을 로드하려고 합니다. 하이버네이트 두 번째 레벨 캐시는 세션 간에 공유되므로 두 번째 레벨 캐시에서 찾아지고 데이터베이스 쿼리가 실행되지 않습니다. 피치 카운트, 미스 카운트 및 풋 카운트는 동일한 상태로 유지되지만 히트 카운트는 2에서 4로 증가합니다.
따라서 우리의 하이버네이트 두 번째 레벨 캐시; 하이버네이트 EHCache; 가 잘 작동하는 것으로 나타났습니다. 하이버네이트 통계는 시스템의 병목 현상을 찾아 최적화하고 캐시에서 더 많은 데이터를로드하여 피치 카운트를 줄이는 데 도움이 됩니다. 여기까지가 하이버네이트 EHCache 예제입니다. EHCache를 하이버네이트 응용 프로그램에서 구성하고 하이버네이트 두 번째 레벨 캐시를 통해 성능을 향상시키는 데 도움이 되기를 바랍니다. 아래 링크에서 샘플 프로젝트를 다운로드하고 다른 통계 데이터를 사용하여 더 많이 배울 수 있습니다.
Source:
https://www.digitalocean.com/community/tutorials/hibernate-ehcache-hibernate-second-level-cache