Hibernate EHCache – Hibernate 二次キャッシュ

Hibernate Second Level Cacheの例チュートリアルへようこそ。今日は、最も人気のあるHibernate Second Level CacheのプロバイダであるHibernate EHCacheを見ていきます。

Hibernate Second Level Cache

大規模なアプリケーションでHibernateを使用する際の主な利点の1つは、キャッシュのサポートにより、データベースクエリの削減とパフォーマンスの向上が可能となることです。以前の例では、Hibernate First Level Cacheを見てきましたが、今日はHibernate EHCacheを使用したHibernate Second Level Cacheを見ていきます。Hibernate Second Level CacheのプロバイダにはEHCacheとInfinispanがありますが、EHCacheがより人気があり、この例プロジェクトではそれを使用します。ただし、プロジェクトに移る前に、オブジェクトをキャッシュするための異なる戦略を知っておく必要があります。

  1. 読み取り専用:このキャッシュ戦略は、常に読み取り専用で更新されない永続オブジェクトに使用する必要があります。アプリケーションの設定や他の静的データを読み取りおよびキャッシュするために適しています。オブジェクトがデータベースで更新されたかどうかを確認するオーバーヘッドがないため、パフォーマンスが最も良い単純な戦略です。
  2. 読み書き:これは、Hibernateアプリケーションによって更新されることができる永続オブジェクトに適しています。ただし、データがバックエンドや他のアプリケーションを介して更新される場合、Hibernateはそれを知る方法がなく、データが古くなる可能性があります。したがって、この戦略を使用する場合は、データの更新にHibernate APIを使用していることを確認してください。
  3. 非制限読み書き:アプリケーションがデータをたまに更新する必要があり、厳密なトランザクション分離が必要ではない場合、非厳格読み書きキャッシュが適している場合があります。
  4. トランザクショナル:トランザクションキャッシュ戦略は、JBoss TreeCacheなどの完全にトランザクショナルなキャッシュプロバイダーをサポートします。このようなキャッシュはJTA環境でのみ使用でき、hibernate.transaction.manager_lookup_classを指定する必要があります。

Hibernate EHCache

EHCacheは上記のすべてのキャッシュ戦略をサポートしているため、Hibernateのセカンドレベルキャッシュを探している場合には最適な選択肢です。EHCacheについては詳細には触れませんが、メインの焦点はHibernateアプリケーションでの動作させることです。Eclipseやお気に入りのIDEでMavenプロジェクトを作成し、最終的な実装は以下の画像のようになります。アプリケーションの各コンポーネントについて個別に見ていきましょう。

Hibernate EHCache Maven Dependencies

ハイバネートのセカンドレベルキャッシュを使用するためには、アプリケーションに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 Second Level Cache – Hibernate EHCache Configuration

ハイバネートのセカンドレベルキャッシュはデフォルトで無効になっているため、有効化し、いくつかの設定を追加する必要があります。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>

ハイバネートのセカンドレベルキャッシュの設定については、以下のポイントに注意してください。

  1. 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です。
  2. hibernate.cache.use_second_level_cacheは、セカンドレベルキャッシュを有効にするために使用されます。
  3. hibernate.cache.use_query_cacheは、クエリキャッシュを有効にするために使用されます。これがない場合、HQLクエリの結果はキャッシュされません。
  4. 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は多くのオプションを提供していますが、詳細には触れませんが、上記のいくつかの重要な設定は次のとおりです:

  1. diskStore: EHCacheはデータをメモリに保存しますが、オーバーフローが発生するとデータをファイルシステムに書き込みます。このプロパティを使用して、EHCacheがオーバーフローしたデータを書き込む場所を定義します。
  2. defaultCache: これは必須の設定であり、オブジェクトをキャッシュする必要があり、キャッシュリージョンが定義されていない場合に使用されます。
  3. cache name=“employee”: キャッシュ要素を使用してリージョンとその設定を定義します。複数のリージョンとそのプロパティを定義できます。モデルビーンのキャッシュプロパティを定義する際に、キャッシュ戦略とリージョンも定義できます。キャッシュプロパティは名前でわかりやすく明確です。
  4. EHCacheからの警告があったため、キャッシュリージョンorg.hibernate.cache.internal.StandardQueryCacheorg.hibernate.cache.spi.UpdateTimestampsCacheが定義されています。

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ユーティリティクラス

私たちはHibernateを設定し、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 {
            // hibernate.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;
    }
}

私たちのHibernateのセカンドレベルキャッシュプロジェクトは準備ができました。それをテストするためにシンプルなプログラムを書きましょう。

Hibernate 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

出力からわかるように、最初は統計情報が無効になっていますが、Hibernateのセカンドレベルキャッシュを確認するために有効にしています。出力のステップバイステップの説明は以下の通りです:

  1. アプリケーションにデータをロードする前は、すべての統計情報が0です(予想どおり)。
  2. id=1のEmployeeを初めてロードすると、まずは一次キャッシュ、次にセカンドレベルキャッシュで検索されます。キャッシュに見つからない場合は、データベースクエリが実行され、フェッチ回数が1になります。オブジェクトがロードされると、一次キャッシュとセカンドレベルキャッシュの両方に保存されます。したがって、セカンドレベルヒット回数は0のままで、ミス回数は1になります。put回数が2になっていることに注意してください。これは、EmployeeオブジェクトにはAddressも含まれているため、両方のオブジェクトがセカンドレベルキャッシュに保存され、回数が2に増えたためです。
  3. 次に、再度id=1のEmployeeをロードしますが、今回は一次キャッシュに存在しています。したがって、データベースクエリは表示されず、他のセカンドレベルキャッシュの統計情報も変わりません。
  4. 次に、evict()メソッドを使用して従業員オブジェクトを一次キャッシュから削除します。そして、それを読み込もうとすると、Hibernateは2次キャッシュで見つけます。そのため、データベースクエリは実行されず、フェッチ回数は1のままです。ヒット回数は、従業員オブジェクトとアドレスオブジェクトの両方が2次キャッシュから読み込まれるため、0から2に増加します。2次キャッシュのミスとプット回数は以前の値のままです。
  5. 次に、id=3の従業員をロードしようとします。データベースクエリが実行され、フェッチ回数は2に増加し、ミス回数は1から2に増加し、プット回数は2から4に増加します。
  6. 次に、別のセッションでid=1の従業員をロードしようとします。Hibernateの2次キャッシュはセッション間で共有されるため、2次キャッシュで見つかり、データベースクエリは実行されません。フェッチ回数、ミス回数、プット回数は同じままで、ヒット回数は2から4に増加します。

したがって、Hibernateの2次キャッシュであるHibernate EHCacheは正常に機能していることがわかりました。Hibernateの統計情報は、システムのボトルネックを見つけ、フェッチ回数を減らし、キャッシュからより多くのデータをロードするために最適化するのに役立ちます。これでHibernate EHCacheの例は以上です。EHCacheをHibernateアプリケーションで設定し、Hibernateの2次キャッシュを使用してパフォーマンスを向上させるのに役立つと思います。以下のリンクからサンプルプロジェクトをダウンロードし、他の統計データを使用してさらに学習することができます。

Hibernate EHCacheプロジェクトをダウンロードする

Source:
https://www.digitalocean.com/community/tutorials/hibernate-ehcache-hibernate-second-level-cache