Hibernate

Spring 環境での Hibernate 5 (英語) のカバレッジから始め、それを使用して、Spring が OR マッパーの統合に向けて取っているアプローチを示します。このセクションでは、多くの課題を詳細にカバーし、DAO 実装のさまざまなバリエーションとトランザクション境界を示します。これらのパターンのほとんどは、サポートされている他のすべての ORM ツールに直接変換できます。この章の後半のセクションでは、他の ORM テクノロジーについて説明し、簡単な例を示します。

Spring Framework 6.0 以降、Spring では、Spring の HibernateJpaVendorAdapter およびネイティブ Hibernate SessionFactory セットアップに Hibernate ORM 5.5+ が必要です。Hibernate 世代の最後のフィーチャーブランチとして、Hibernate ORM 5.6 を推奨します。

Hibernate ORM 6.x は、JPA プロバイダー (HibernateJpaVendorAdapter) としてのみサポートされます。orm.hibernate5 パッケージを使用したプレーン SessionFactory セットアップはサポートされなくなりました。新しい開発プロジェクトには、JPA スタイルのセットアップを備えた Hibernate ORM 6.1/6.2 をお勧めします。

Spring コンテナーでの SessionFactory セットアップ

アプリケーションオブジェクトをハードコードされたリソースルックアップに結び付けないようにするには、リソース(JDBC DataSource または Hibernate SessionFactory など)を Spring コンテナー内の Bean として定義できます。リソースにアクセスする必要があるアプリケーションオブジェクトは、次のセクションの DAO 定義に示すように、Bean 参照を介してそのような事前定義されたインスタンスへの参照を受け取ります。

XML アプリケーションコンテキスト定義からの次の抜粋は、その上に JDBC DataSource および Hibernate SessionFactory をセットアップする方法を示しています。

<beans>

	<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
		<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
		<property name="username" value="sa"/>
		<property name="password" value=""/>
	</bean>

	<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
		<property name="dataSource" ref="myDataSource"/>
		<property name="mappingResources">
			<list>
				<value>product.hbm.xml</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<value>
				hibernate.dialect=org.hibernate.dialect.HSQLDialect
			</value>
		</property>
	</bean>

</beans>

次の例に示すように、ローカル Jakarta Commons DBCP BasicDataSource から JNDI に配置された DataSource (通常はアプリケーションサーバーによって管理される)への切り替えは、構成の問題にすぎません。

<beans>
	<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>

Spring の JndiObjectFactoryBean / <jee:jndi-lookup> を使用して、JNDI にある SessionFactory にアクセスし、それを取得して公開することもできます。ただし、これは通常、EJB コンテキスト以外では一般的ではありません。

Spring は LocalSessionFactoryBuilder バリアントも提供し、@Bean スタイルの構成およびプログラムによるセットアップとシームレスに統合します(FactoryBean は含まれません)。

LocalSessionFactoryBean と LocalSessionFactoryBuilder は両方ともバックグラウンドブートストラップをサポートし、Hibernate の初期化は特定のブートストラップエグゼキューター(SimpleAsyncTaskExecutor など)のアプリケーションブートストラップスレッドと並行して実行されます。LocalSessionFactoryBean では、これは bootstrapExecutor プロパティを介して利用できます。プログラムによる LocalSessionFactoryBuilder には、ブートストラップエグゼキューター引数を取るオーバーロード buildSessionFactory メソッドがあります。

このようなネイティブ Hibernate セットアップでは、ネイティブ Hibernate アクセスの次に、標準 JPA インタラクション用の JPA EntityManagerFactory を公開することもできます。詳細については、JPA のネイティブ Hibernate セットアップを参照してください。

プレーン Hibernate API に基づいた DAO の実装

Hibernate にはコンテキストセッションと呼ばれる機能があり、Hibernate 自体がトランザクションごとに 1 つの現在の Session を管理します。これは、トランザクションごとに 1 つの Hibernate Session の Spring の同期とほぼ同等です。対応する DAO 実装は、プレーンな Hibernate API に基づいた次の例に似ています。

  • Java

  • Kotlin

public class ProductDaoImpl implements ProductDao {

	private SessionFactory sessionFactory;

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public Collection loadProductsByCategory(String category) {
		return this.sessionFactory.getCurrentSession()
				.createQuery("from test.Product product where product.category=?")
				.setParameter(0, category)
				.list();
	}
}
class ProductDaoImpl(private val sessionFactory: SessionFactory) : ProductDao {

	fun loadProductsByCategory(category: String): Collection<*> {
		return sessionFactory.currentSession
				.createQuery("from test.Product product where product.category=?")
				.setParameter(0, category)
				.list()
	}
}

このスタイルは、SessionFactory をインスタンス変数に保持することを除いて、Hibernate リファレンスドキュメントおよび例のスタイルと似ています。Hibernate の CaveatEmptor サンプルアプリケーションの古い学校の staticHibernateUtil クラスよりも、このようなインスタンスベースのセットアップを強くお勧めします。(一般的に、どうしても必要な場合を除き、static 変数にリソースを保持しないでください。)

上記の DAO の例は、依存性注入パターンに従います。Spring の HibernateTemplate に対してコーディングされている場合のように、Spring IoC コンテナーにうまく収まります。このような DAO をプレーン Java で(たとえば、単体テストで)設定することもできます。これを行うには、インスタンス化して、目的のファクトリリファレンスを使用して setSessionFactory(..) を呼び出します。Spring Bean の定義として、DAO は次のようになります。

<beans>

	<bean id="myProductDao" class="product.ProductDaoImpl">
		<property name="sessionFactory" ref="mySessionFactory"/>
	</bean>

</beans>

この DAO スタイルの主な利点は、Hibernate API のみに依存することです。Spring クラスのインポートは必要ありません。これは非侵襲性の観点から魅力的であり、Hibernate 開発者にとってより自然に感じるかもしれません。

ただし、DAO はプレーンな HibernateException をスローします(チェックされていないため、宣言またはキャッチする必要はありません)。つまり、呼び出し元は、Hibernate の例外階層に依存する場合を除き、例外を一般に致命的なものとしてのみ処理できます。特定の原因(楽観的ロックの失敗など)をキャッチすることは、呼び出し元を実装戦略に結び付けない限り不可能です。このトレードオフは、強力な Hibernate ベースのアプリケーション、特別な例外処理を必要としない、その両方のアプリケーションに受け入れられる場合があります。

幸い、Spring の LocalSessionFactoryBean は、Hibernate の SessionFactory.getCurrentSession() メソッドを Spring トランザクション戦略でサポートし、現在の Spring 管理のトランザクション Session を HibernateTransactionManager でも返します。そのメソッドの標準的な動作は、進行中の JTA トランザクションに関連付けられている現在の Session を返します(存在する場合)。この動作は、Spring の JtaTransactionManager、EJB コンテナー管理トランザクション(CMT)、または JTA のいずれを使用するかに関係なく適用されます。

要約すると、Spring が管理するトランザクションに参加しながら、プレーンな Hibernate API に基づいて DAO を実装できます。

宣言的な取引区分

Spring の宣言的なトランザクションサポートを使用することをお勧めします。これにより、Java コードの明示的なトランザクション境界設定 API 呼び出しを AOP トランザクションインターセプターに置き換えることができます。Java アノテーションまたは XML を使用して、Spring コンテナーでこのトランザクションインターセプターを構成できます。この宣言的なトランザクション機能により、ビジネスサービスを繰り返しトランザクション境界コードから解放し、アプリケーションの真の価値であるビジネスロジックの追加に集中できます。

続行する前に、宣言的トランザクション管理をまだ読んでいない場合は読むことを強くお勧めします。

サービスレイヤーに @Transactional アノテーションを付けて、Spring コンテナーにこれらのアノテーションを見つけ、これらのアノテーション付きメソッドにトランザクションセマンティクスを提供するように指示できます。次の例は、その方法を示しています。

  • Java

  • Kotlin

public class ProductServiceImpl implements ProductService {

	private ProductDao productDao;

	public void setProductDao(ProductDao productDao) {
		this.productDao = productDao;
	}

	@Transactional
	public void increasePriceOfAllProductsInCategory(final String category) {
		List productsToChange = this.productDao.loadProductsByCategory(category);
		// ...
	}

	@Transactional(readOnly = true)
	public List<Product> findAllProducts() {
		return this.productDao.findAllProducts();
	}
}
class ProductServiceImpl(private val productDao: ProductDao) : ProductService {

	@Transactional
	fun increasePriceOfAllProductsInCategory(category: String) {
		val productsToChange = productDao.loadProductsByCategory(category)
		// ...
	}

	@Transactional(readOnly = true)
	fun findAllProducts() = productDao.findAllProducts()
}

コンテナーで、PlatformTransactionManager 実装(Bean として)および <tx:annotation-driven/> エントリをセットアップし、実行時に @Transactional 処理を選択する必要があります。次の例は、その方法を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/tx
		https://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- SessionFactory, DataSource, etc. omitted -->

	<bean id="transactionManager"
			class="org.springframework.orm.hibernate5.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>

	<tx:annotation-driven/>

	<bean id="myProductService" class="product.SimpleProductService">
		<property name="productDao" ref="myProductDao"/>
	</bean>

</beans>

プログラムによるトランザクション区分

任意の数の操作にまたがる低レベルのデータアクセスサービスに加えて、アプリケーションの高レベルでトランザクションの境界を定めることができます。周囲のビジネスサービスの実装にも制限はありません。Spring PlatformTransactionManager のみが必要です。ここでも、後者はどこからでも取得できますが、setTransactionManager(..) メソッドを介した Bean 参照として取得することが望ましいです。また、productDAO は setProductDao(..) メソッドで設定する必要があります。次のスニペットのペアは、Spring アプリケーションコンテキストでのトランザクションマネージャーとビジネスサービス定義、およびビジネスメソッド実装の例を示しています。

<beans>

	<bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
		<property name="sessionFactory" ref="mySessionFactory"/>
	</bean>

	<bean id="myProductService" class="product.ProductServiceImpl">
		<property name="transactionManager" ref="myTxManager"/>
		<property name="productDao" ref="myProductDao"/>
	</bean>

</beans>
  • Java

  • Kotlin

public class ProductServiceImpl implements ProductService {

	private TransactionTemplate transactionTemplate;
	private ProductDao productDao;

	public void setTransactionManager(PlatformTransactionManager transactionManager) {
		this.transactionTemplate = new TransactionTemplate(transactionManager);
	}

	public void setProductDao(ProductDao productDao) {
		this.productDao = productDao;
	}

	public void increasePriceOfAllProductsInCategory(final String category) {
		this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
			public void doInTransactionWithoutResult(TransactionStatus status) {
				List productsToChange = this.productDao.loadProductsByCategory(category);
				// do the price increase...
			}
		});
	}
}
class ProductServiceImpl(transactionManager: PlatformTransactionManager,
						private val productDao: ProductDao) : ProductService {

	private val transactionTemplate = TransactionTemplate(transactionManager)

	fun increasePriceOfAllProductsInCategory(category: String) {
		transactionTemplate.execute {
			val productsToChange = productDao.loadProductsByCategory(category)
			// do the price increase...
		}
	}
}

Spring の TransactionInterceptor では、チェックされたアプリケーションの例外をコールバックコードでスローできますが、TransactionTemplate はコールバック内の未チェックの例外に制限されています。TransactionTemplate は、未チェックのアプリケーション例外の場合、またはトランザクションがアプリケーションによってロールバック専用としてマークされている場合(TransactionStatus を設定することにより)、ロールバックをトリガーします。デフォルトでは、TransactionInterceptor は同じように動作しますが、メソッドごとに構成可能なロールバックポリシーを許可します。

トランザクション管理戦略

TransactionTemplate と TransactionInterceptor はどちらも、実際のトランザクション処理を PlatformTransactionManager インスタンス(内部で ThreadLocalSession を使用して HibernateTransactionManager (単一の Hibernate SessionFactory の場合)にすることができます)または Hibernate の JtaTransactionManager (コンテナーの JTA サブシステムに委譲する)に委譲します。アプリケーション。カスタム PlatformTransactionManager 実装を使用することもできます。ネイティブ Hibernate トランザクション管理から JTA への切り替え(アプリケーションの特定のデプロイの分散トランザクション要件に直面している場合など)は、構成の問題のみです。Hibernate トランザクションマネージャーを Spring の JTA トランザクション実装に置き換えることができます。トランザクション境界とデータアクセスコードはどちらも、汎用のトランザクション管理 API を使用しているため、変更なしで機能します。

複数の Hibernate セッションファクトリにわたる分散トランザクションの場合、JtaTransactionManager をトランザクション戦略として複数の LocalSessionFactoryBean 定義と組み合わせることができます。各 DAO は、対応する Bean プロパティに渡される 1 つの特定の SessionFactory 参照を取得します。基礎となるすべての JDBC データソースがトランザクションコンテナーのものである場合、ビジネスサービスは、JtaTransactionManager を戦略として使用している限り、特に考慮せずに、任意の数の DAO と任意の数のセッションファクトリにまたがってトランザクションを区別できます

HibernateTransactionManager と JtaTransactionManager はどちらも、コンテナー固有のトランザクションマネージャールックアップまたは JCA コネクター(EJB を使用してトランザクションを開始しない場合)を使用せずに、Hibernate で適切な JVM レベルのキャッシュ処理を可能にします。

HibernateTransactionManager は、Hibernate JDBC Connection を特定の DataSource のプレーンな JDBC アクセスコードにエクスポートできます。この機能により、Hibernate と JDBC データが混在し、JTA なしで完全にアクセスできる高レベルのトランザクション境界設定が可能になります。ただし、1 つのデータベースにのみアクセスできます。LocalSessionFactoryBean クラスの dataSource プロパティを介して DataSource で渡された SessionFactory をセットアップした場合、HibernateTransactionManager は Hibernate トランザクションを JDBC トランザクションとして自動的に公開します。または、HibernateTransactionManager クラスの dataSource プロパティを介して、トランザクションが公開されることになっている DataSource を明示的に指定できます。

実際のリソース接続の JTA スタイルの遅延取得のために、Spring はターゲット接続プールに対応する DataSource プロキシクラスを提供します。LazyConnectionDataSourceProxy (Javadoc) を参照してください。これは、データベースにアクセスするのではなく、ローカルキャッシュから処理できることが多い Hibernate 読み取り専用トランザクションに特に役立ちます。

コンテナー管理のリソースとローカルで定義されたリソースの比較

アプリケーションコードの 1 行を変更することなく、コンテナー管理の JNDI SessionFactory とローカルで定義された JNDI SessionFactory を切り替えることができます。リソース定義をコンテナー内に保持するか、アプリケーション内でローカルに保持するかは、主に使用するトランザクション戦略の問題です。Spring で定義されたローカル SessionFactory と比較して、手動で登録された JNDI SessionFactory は利点を提供しません。Hibernate の JCA コネクターを介して SessionFactory をデプロイすると、Jakarta EE サーバーの管理インフラストラクチャに参加する付加価値が提供されますが、それ以上の実際の価値は追加されません。

Spring のトランザクションサポートはコンテナーにバインドされていません。JTA 以外の戦略で構成されている場合、トランザクションサポートはスタンドアロンまたはテスト環境でも機能します。特に単一データベーストランザクションの典型的なケースでは、Spring の単一リソースローカルトランザクションサポートは、JTA の軽量で強力な代替手段です。ローカル EJB ステートレスセッション Bean を使用してトランザクションを駆動する場合、単一のデータベースにのみアクセスし、ステートレスセッション Bean のみを使用してコンテナー管理トランザクションを介して宣言型トランザクションを提供する場合でも、EJB コンテナーと JTA の両方に依存します。プログラムで JTA を直接使用するには、Jakarta EE 環境も必要です。

Spring 駆動型トランザクションは、ローカルで定義された Hibernate SessionFactory でも、ローカルの JDBC DataSource と同様に機能します。ただし、単一のデータベースにアクセスする必要があります。分散トランザクション要件がある場合にのみ、Spring の JTA トランザクション戦略を使用する必要があります。JCA コネクターには、コンテナー固有のデプロイステップが必要であり、そもそも(明らかに)JCA サポートが必要です。この構成では、ローカルリソース定義と Spring 駆動型トランザクションを使用して単純な Web アプリケーションをデプロイするよりも多くの作業が必要です。

EJB を使用しない場合は、すべてを考慮して、ローカル SessionFactory セットアップと Spring の HibernateTransactionManager または JtaTransactionManager を使用してください。コンテナーデプロイの不便さなしに、適切なトランザクション JVM レベルのキャッシュや分散トランザクションなど、すべての利点が得られます。JCA コネクターを介した Hibernate SessionFactory の JNDI 登録は、EJB と組み合わせて使用する場合にのみ価値を追加します。

Hibernate を使用した偽のアプリケーションサーバー警告

非常に厳密な XADataSource 実装を備えた一部の JTA 環境(現在、一部の WebLogic サーバーおよび WebSphere バージョン)では、その環境の JTA トランザクションマネージャーに関係なく Hibernate が構成されている場合、誤った警告または例外がアプリケーションサーバーログに表示されることがあります。これらの警告または例外は、おそらくトランザクションがアクティブでなくなったために、アクセスされている接続が無効であるか、JDBC アクセスが無効であることを示しています。例として、WebLogic からの実際の例外を次に示します。

java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No
further JDBC access is allowed within this transaction.

もう 1 つの一般的な問題は、JTA トランザクション後の接続リークであり、Hibernate セッション(および潜在的に基礎となる JDBC 接続)が適切に閉じられません。

このような課題は、Hibernate に(Spring とともに)同期する JTA トランザクションマネージャーを認識させることで解決できます。これを行うには、2 つのオプションがあります。

  • Spring JtaTransactionManager Bean を Hibernate セットアップに渡します。最も簡単な方法は、LocalSessionFactoryBean Bean の jtaTransactionManager プロパティへの Bean 参照です(Hibernate トランザクション設定を参照)。次に、Spring は、対応する JTA 戦略を Hibernate で使用できるようにします。

  • また、Hibernate の JTA 関連のプロパティ、特に LocalSessionFactoryBean の "hibernateProperties" の "hibernate.transaction.coordinator_class" ,"hibernate.connection.handling_mode"、および場合によっては "hibernate.transaction.jta.platform" を明示的に構成することもできます (これらのプロパティの詳細については、Hibernate のマニュアルを参照してください)。

このセクションの残りの部分では、Hibernate が JTA PlatformTransactionManager を認識している場合と認識していない場合に発生するイベントのシーケンスについて説明します。

Hibernate が JTA トランザクションマネージャーを認識して設定されていない場合、JTA トランザクションがコミットすると次のイベントが発生します。

  • JTA トランザクションがコミットします。

  • Spring の JtaTransactionManager は JTA トランザクションに同期されているため、JTA トランザクションマネージャーによって afterCompletion コールバックを通じてコールバックされます。

  • その他のアクティビティでは、この同期化によって Spring から Hibernate へのコールバックが Hibernate の afterTransactionCompletion コールバック (Hibernate キャッシュのクリアに使用) を介してトリガーされ、その後 Hibernate セッションで明示的な close() コールが行われて、Hibernate が JDBC 接続の close() を試行するようになります。

  • 一部の環境では、トランザクションがすでにコミットされているため、アプリケーションサーバーは Connection が使用可能であると見なしなくなるため、この Connection.close() 呼び出しは警告またはエラーをトリガーします。

Hibernate が JTA トランザクションマネージャーを認識して設定されている場合、JTA トランザクションがコミットすると次のイベントが発生します。

  • JTA トランザクションをコミットする準備ができました。

  • Spring の JtaTransactionManager は JTA トランザクションに同期されるため、トランザクションは JTA トランザクションマネージャーによって beforeCompletion コールバックを通じてコールバックされます。

  • Spring は、Hibernate 自体が JTA トランザクションに同期され、前のシナリオとは異なる動作をすることを認識しています。特に、Hibernate のトランザクションリソース管理と連携しています。

  • JTA トランザクションがコミットします。

  • Hibernate は JTA トランザクションに同期されるため、トランザクションは JTA トランザクションマネージャーによって afterCompletion コールバックを介してコールバックされ、そのキャッシュを適切にクリアできます。