Hibernate
Spring 環境での Hibernate 5 (英語) のカバレッジから始め、それを使用して、Spring が OR マッパーの統合に向けて取っているアプローチを示します。このセクションでは、多くの課題を詳細にカバーし、DAO 実装のさまざまなバリエーションとトランザクション境界を示します。これらのパターンのほとんどは、サポートされている他のすべての ORM ツールに直接変換できます。この章の後半のセクションでは、他の ORM テクノロジーについて説明し、簡単な例を示します。
Spring Framework 6.0 以降、Spring では、Spring の Hibernate ORM 6.x は、JPA プロバイダー ( |
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 は
このようなネイティブ Hibernate セットアップでは、ネイティブ Hibernate アクセスの次に、標準 JPA インタラクション用の JPA |
プレーン 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 サンプルアプリケーションの古い学校の static
HibernateUtil
クラスよりも、このようなインスタンスベースのセットアップを強くお勧めします。(一般的に、どうしても必要な場合を除き、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
インスタンス(内部で ThreadLocal
Session
を使用して 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
コールバックを介してコールバックされ、そのキャッシュを適切にクリアできます。