Hibernate

我们从 Spring 环境中的 Hibernate 5 涵盖范围开始,使用它来展示 Spring 用于集成 OR 映射器的做法。本部分详细介绍了许多问题,并展示了 DAO 实施和事务分解的不同变化。大多数这些模式都可以直接翻译成所有其他受支持的 ORM 工具。然后本章后面部分将涵盖其他 ORM 技术并展示一些简短示例。

We start with a coverage of Hibernate 5 in a Spring environment, using it to demonstrate the approach that Spring takes towards integrating OR mappers. This section covers many issues in detail and shows different variations of DAO implementations and transaction demarcation. Most of these patterns can be directly translated to all other supported ORM tools. The later sections in this chapter then cover the other ORM technologies and show brief examples.

从 Spring Framework 6.0 开始,Spring 要求使用 Spring 的 HibernateJpaVendorAdapter 以及本机 Hibernate SessionFactory 设置,需要 Hibernate ORM 5.5+。我们推荐使用 Hibernate ORM 5.6 作为该 Hibernate 代中的最后一个功能分支。

As of Spring Framework 6.0, Spring requires Hibernate ORM 5.5+ for Spring’s HibernateJpaVendorAdapter as well as for a native Hibernate SessionFactory setup. We recommend Hibernate ORM 5.6 as the last feature branch in that Hibernate generation.

Hibernate ORM 6.x 仅受支持作为 JPA 提供程序 (HibernateJpaVendorAdapter)。不再支持带有 orm.hibernate5 包的普通 SessionFactory 设置。我们建议将 Hibernate ORM 6.1/6.2 与 JPA 风格的设置一起用于新的开发项目。

Hibernate ORM 6.x is only supported as a JPA provider (HibernateJpaVendorAdapter). Plain SessionFactory setup with the orm.hibernate5 package is not supported anymore. We recommend Hibernate ORM 6.1/6.2 with JPA-style setup for new development projects.

SessionFactory Setup in a Spring Container

为了避免将应用程序对象与硬编码的资源查找相关联,您可以在 Spring 容器中将资源(如 JDBC DataSource 或 Hibernate SessionFactory)定义为 bean。需要访问资源的应用程序对象将通过 bean 引用接收对这些预定义实例的引用,如 next section 中的 DAO 定义中所示。

To avoid tying application objects to hard-coded resource lookups, you can define resources (such as a JDBC DataSource or a Hibernate SessionFactory) as beans in the Spring container. Application objects that need to access resources receive references to such predefined instances through bean references, as illustrated in the DAO definition in the next section.

XML 应用程序上下文定义中的以下摘录显示了如何在 JDBC DataSource 的基础上设置 Hibernate SessionFactory

The following excerpt from an XML application context definition shows how to set up a JDBC DataSource and a Hibernate SessionFactory on top of it:

<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(通常由应用程序服务器管理)仅仅是一个配置问题,如下例所示:

Switching from a local Jakarta Commons DBCP BasicDataSource to a JNDI-located DataSource (usually managed by an application server) is only a matter of configuration, as the following example shows:

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

您还可以通过 Spring 的 JndiObjectFactoryBean / <jee:jndi-lookup> 来检索和公开 JNDI 定位的 SessionFactory。但是,在 EJB 上下文之外,这通常并不常见。

You can also access a JNDI-located SessionFactory, using Spring’s JndiObjectFactoryBean / <jee:jndi-lookup> to retrieve and expose it. However, that is typically not common outside of an EJB context.

Spring 还提供了一个 LocalSessionFactoryBuilder 变体,它与 @Bean 风格配置和编程设置(不涉及 FactoryBean)无缝集成。

Spring also provides a LocalSessionFactoryBuilder variant, seamlessly integrating with @Bean style configuration and programmatic setup (no FactoryBean involved).

LocalSessionFactoryBeanLocalSessionFactoryBuilder 都支持后台自举,其中 Hibernate 初始化与应用程序自举线程并行运行在给定的自举执行器(例如 SimpleAsyncTaskExecutor)上。在 LocalSessionFactoryBean 上,可以通过 bootstrapExecutor 属性使用此功能。在编程 LocalSessionFactoryBuilder 上,有一个重载的 buildSessionFactory 方法,它采用自举执行器参数。

Both LocalSessionFactoryBean and LocalSessionFactoryBuilder support background bootstrapping, with Hibernate initialization running in parallel to the application bootstrap thread on a given bootstrap executor (such as a SimpleAsyncTaskExecutor). On LocalSessionFactoryBean, this is available through the bootstrapExecutor property. On the programmatic LocalSessionFactoryBuilder, there is an overloaded buildSessionFactory method that takes a bootstrap executor argument.

从 Spring Framework 5.1 开始,这样的本机 Hibernate 设置也可以公开一个 JPA EntityManagerFactory 以进行标准 JPA 交互,此外,该 JPA 交互与本机 Hibernate 访问并行进行。请参见 Native Hibernate Setup for JPA 了解更多详情。

As of Spring Framework 5.1, such a native Hibernate setup can also expose a JPA EntityManagerFactory for standard JPA interaction next to native Hibernate access. See Native Hibernate Setup for JPA for details.

Implementing DAOs Based on the Plain Hibernate API

Hibernate 具有一个称为上下文会话的功能,其中 Hibernate 本身管理每个事务的一个当前 Session。这大致相当于 Spring 的每事务同步一个 Hibernate Session。相应的 DAO 实现类似于以下示例,它基于简单的 Hibernate API:

Hibernate has a feature called contextual sessions, wherein Hibernate itself manages one current Session per transaction. This is roughly equivalent to Spring’s synchronization of one Hibernate Session per transaction. A corresponding DAO implementation resembles the following example, based on the plain 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()
	}
}

此样式类似于 Hibernate 参考文档和示例,除了在实例变量中保存 SessionFactory 之外。我们强烈推荐这种基于实例的设置,而不是 Hibernate 的 CaveatEmptor 示例应用程序中的老式 static HibernateUtil 类。(一般来说,除非绝对必要,否则不要在 static 变量中保留任何资源。)

This style is similar to that of the Hibernate reference documentation and examples, except for holding the SessionFactory in an instance variable. We strongly recommend such an instance-based setup over the old-school static HibernateUtil class from Hibernate’s CaveatEmptor sample application. (In general, do not keep any resources in static variables unless absolutely necessary.)

前面的 DAO 示例遵循依赖注入模式。它很好地融入 Spring IoC 容器,就好像针对 Spring 的 HibernateTemplate 编写代码一样。您还可以在普通 Java 中设置这样的 DAO(例如,在单元测试中)。要执行此操作,请对其进行实例化,然后用所需的工厂引用调用 setSessionFactory(..)。作为 Spring bean 定义,DAO 类似于以下内容:

The preceding DAO example follows the dependency injection pattern. It fits nicely into a Spring IoC container, as it would if coded against Spring’s HibernateTemplate. You can also set up such a DAO in plain Java (for example, in unit tests). To do so, instantiate it and call setSessionFactory(..) with the desired factory reference. As a Spring bean definition, the DAO would resemble the following:

<beans>

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

</beans>

这种 DAO 样式的主要优点是它仅依赖于 Hibernate API。不需要导入任何 Spring 类。从非侵入性的角度来看,这很有吸引力,并且 Hibernate 开发人员可能会觉得更自然。

The main advantage of this DAO style is that it depends on Hibernate API only. No import of any Spring class is required. This is appealing from a non-invasiveness perspective and may feel more natural to Hibernate developers.

但是,DAO 会引发普通的 HibernateException(这是未经检查的,因此不必声明或捕获),这意味着调用者只能将异常视为通常致命严重——除非他们希望依赖 Hibernate 自己异常层次结构。如果不将调用者绑定到实现策略,就不可能捕获特定原因(例如乐观锁定失败)。这种权衡可能是可接受的,对于基于 Hibernate 的应用程序、不需要任何特殊异常处理或两者兼备的应用程序。

However, the DAO throws plain HibernateException (which is unchecked, so it does not have to be declared or caught), which means that callers can treat exceptions only as being generally fatal — unless they want to depend on Hibernate’s own exception hierarchy. Catching specific causes (such as an optimistic locking failure) is not possible without tying the caller to the implementation strategy. This trade off might be acceptable to applications that are strongly Hibernate-based, do not need any special exception treatment, or both.

幸运的是,Spring 的 LocalSessionFactoryBean 支持适用于任何 Spring 事务策略的 Hibernate 的 SessionFactory.getCurrentSession() 方法,即使使用 HibernateTransactionManager 也会返回当前由 Spring 管理的事务 Session。该方法的标准行为仍然是返回与进行中的 JTA 事务(如果有)关联的当前 Session。无论您使用 Spring 的 JtaTransactionManager、EJB 容器管理事务 (CMT) 还是 JTA,此行为都会应用。

Fortunately, Spring’s LocalSessionFactoryBean supports Hibernate’s SessionFactory.getCurrentSession() method for any Spring transaction strategy, returning the current Spring-managed transactional Session, even with HibernateTransactionManager. The standard behavior of that method remains to return the current Session associated with the ongoing JTA transaction, if any. This behavior applies regardless of whether you use Spring’s JtaTransactionManager, EJB container managed transactions (CMTs), or JTA.

总之,您可以基于简单的 Hibernate API 实现 DAO,同时仍然能够参与 Spring 管理的事务。

In summary, you can implement DAOs based on the plain Hibernate API, while still being able to participate in Spring-managed transactions.

Declarative Transaction Demarcation

我们建议您使用 Spring 的声明式事务支持,它允许您用 AOP 事务拦截器替换 Java 代码中的显式事务划分 API 调用。您可以使用 Java 注解或 XML 在 Spring 容器中配置此事务拦截器。此声明式事务功能使您可以让业务服务免受重复的事务划分代码的困扰,而专注于添加业务逻辑,而这正是您应用程序的真正价值。

We recommend that you use Spring’s declarative transaction support, which lets you replace explicit transaction demarcation API calls in your Java code with an AOP transaction interceptor. You can configure this transaction interceptor in a Spring container by using either Java annotations or XML. This declarative transaction capability lets you keep business services free of repetitive transaction demarcation code and focus on adding business logic, which is the real value of your application.

在您继续之前,如果您尚未阅读 Declarative Transaction Management,我们强烈建议您阅读。

Before you continue, we are strongly encourage you to read Declarative Transaction Management if you have not already done so.

您可以使用 @Transactional 注解对服务层进行注释,并指示 Spring 容器查找这些注解并为这些带注解的方法提供事务语义。以下示例演示如何执行此操作:

You can annotate the service layer with @Transactional annotations and instruct the Spring container to find these annotations and provide transactional semantics for these annotated methods. The following example shows how to do so:

  • 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 处理。以下示例演示如何执行此操作:

In the container, you need to set up the PlatformTransactionManager implementation (as a bean) and a <tx:annotation-driven/> entry, opting into @Transactional processing at runtime. The following example shows how to do so:

<?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>

Programmatic Transaction Demarcation

您可以在应用程序的较高层划分事务,在跨越任意数量操作的较低层数据访问服务之上。对于周围业务服务的实现也没有限制。它只需要一个 Spring PlatformTransactionManager。同样,后者可以来自任何地方,但最好通过 setTransactionManager(..) 方法作为 bean 引用。此外,productDAO 应该由 setProductDao(..) 方法设置。以下两段代码片断显示了 Spring 应用程序上下文中事务管理器和业务服务定义,以及业务方法实现的示例:

You can demarcate transactions in a higher level of the application, on top of lower-level data access services that span any number of operations. Nor do restrictions exist on the implementation of the surrounding business service. It needs only a Spring PlatformTransactionManager. Again, the latter can come from anywhere, but preferably as a bean reference through a setTransactionManager(..) method. Also, the productDAO should be set by a setProductDao(..) method. The following pair of snippets show a transaction manager and a business service definition in a Spring application context and an example for a business method implementation:

<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 的行为方式相同,但允许对每个方法配置回滚策略。

Spring’s TransactionInterceptor lets any checked application exception be thrown with the callback code, while TransactionTemplate is restricted to unchecked exceptions within the callback. TransactionTemplate triggers a rollback in case of an unchecked application exception or if the transaction is marked rollback-only by the application (by setting TransactionStatus). By default, TransactionInterceptor behaves the same way but allows configurable rollback policies per method.

Transaction Management Strategies

TransactionTemplateTransactionInterceptor 都将实际的事务处理委派给 PlatformTransactionManager 实例(它可以是 HibernateTransactionManager(用于单个 Hibernate SessionFactory)),方法是在底层使用 ThreadLocal Session,或一个 JtaTransactionManager(委派到 Hibernate 应用程序的容器的 JTA 子系统)。你甚至可以使用定制的 PlatformTransactionManager 实现。从 Hibernate 原生事务管理切换到 JTA(比如在你的应用程序的特定部署中遇到分布式事务需求时),只需要进行配置。你可以用 Spring 的 JTA 事务实现来替换 Hibernate 事务管理器。事务分界和数据访问代码不需要改动,因为它们使用的是通用的事务管理 API。

Both TransactionTemplate and TransactionInterceptor delegate the actual transaction handling to a PlatformTransactionManager instance (which can be a HibernateTransactionManager (for a single Hibernate SessionFactory) by using a ThreadLocal Session under the hood) or a JtaTransactionManager (delegating to the JTA subsystem of the container) for Hibernate applications. You can even use a custom PlatformTransactionManager implementation. Switching from native Hibernate transaction management to JTA (such as when facing distributed transaction requirements for certain deployments of your application) is only a matter of configuration. You can replace the Hibernate transaction manager with Spring’s JTA transaction implementation. Both transaction demarcation and data access code work without changes, because they use the generic transaction management APIs.

对于跨多个 Hibernate 会话工厂的分布式事务,可以将 JtaTransactionManager 作为事务策略与多个 LocalSessionFactoryBean 定义结合使用。然后,每个 DAO 都将其特定的 SessionFactory 引用传递到其对应的 Bean 属性中。如果所有底层 JDBC 数据源都是事务性容器数据源,一个业务服务就可以为任意数量的 DAO 和任意数量的会话工厂划分事务,而无需特别注意,只要它将 JtaTransactionManager 用于该策略即可。

For distributed transactions across multiple Hibernate session factories, you can combine JtaTransactionManager as a transaction strategy with multiple LocalSessionFactoryBean definitions. Each DAO then gets one specific SessionFactory reference passed into its corresponding bean property. If all underlying JDBC data sources are transactional container ones, a business service can demarcate transactions across any number of DAOs and any number of session factories without special regard, as long as it uses JtaTransactionManager as the strategy.

HibernateTransactionManagerJtaTransactionManager 都允许使用 Hibernate 来适当处理 JVM 级缓存,而不需要容器特定的事务管理器查找或 JCA 连接器(如果你不使用 EJB 来初始化事务)。

Both HibernateTransactionManager and JtaTransactionManager allow for proper JVM-level cache handling with Hibernate, without container-specific transaction manager lookup or a JCA connector (if you do not use EJB to initiate transactions).

HibernateTransactionManager 可以将 Hibernate JDBC Connection 导出到针对特定 DataSource 的普通 JDBC 访问代码中。如果你只访问一个数据库,那么这种能力允许对混合的 Hibernate 和 JDBC 数据访问进行高级事务分界,而无需 JTA。如果你通过 LocalSessionFactoryBean 类的 dataSource 属性使用 DataSource 来设置传入的 SessionFactory,那么 HibernateTransactionManager 会自动将 Hibernate 事务公开为 JDBC 事务。或者,你可以通过 HibernateTransactionManager 类的 dataSource 属性明确指定要将事务公开到的 DataSource

HibernateTransactionManager can export the Hibernate JDBC Connection to plain JDBC access code for a specific DataSource. This ability allows for high-level transaction demarcation with mixed Hibernate and JDBC data access completely without JTA, provided you access only one database. HibernateTransactionManager automatically exposes the Hibernate transaction as a JDBC transaction if you have set up the passed-in SessionFactory with a DataSource through the dataSource property of the LocalSessionFactoryBean class. Alternatively, you can specify explicitly the DataSource for which the transactions are supposed to be exposed through the dataSource property of the HibernateTransactionManager class.

对于实际资源连接的 JTA 样式延迟检索,Spring 为目标连接池提供了相应的 DataSource 代理类:请参阅 LazyConnectionDataSourceProxy。这对于 Hibernate 只读事务特别有用,该事务通常可以从本地缓存中进行处理,而不是命中数据库。

For JTA-style lazy retrieval of actual resource connections, Spring provides a corresponding DataSource proxy class for the target connection pool: see LazyConnectionDataSourceProxy. This is particularly useful for Hibernate read-only transactions which can often be processed from a local cache rather than hitting the database.

Comparing Container-managed and Locally Defined Resources

你可以在容器管理的 JNDI SessionFactory 和本地定义的 SessionFactory 之间切换,而无需改动一行应用程序代码。将资源定义放在容器中还是放在应用程序中主要取决于你所使用的事务策略。与 Spring 定义的本地 SessionFactory 相比,手动注册的 JNDI SessionFactory 没有任何好处。通过 Hibernate 的 JCA 连接器部署 SessionFactory 会带来额外的价值,即参与 Jakarta EE 服务器的管理基础设施,但除此之外并没有增加实际价值。

You can switch between a container-managed JNDI SessionFactory and a locally defined one without having to change a single line of application code. Whether to keep resource definitions in the container or locally within the application is mainly a matter of the transaction strategy that you use. Compared to a Spring-defined local SessionFactory, a manually registered JNDI SessionFactory does not provide any benefits. Deploying a SessionFactory through Hibernate’s JCA connector provides the added value of participating in the Jakarta EE server’s management infrastructure, but does not add actual value beyond that.

Spring 的事务支持并不局限于容器。当使用除 JTA 之外的任何策略配置时,事务支持也能在独立或测试环境中工作。在典型的单数据库事务的场景中,Spring 的单资源本地事务支持是 JTA 的轻量级高效替代品。当你使用本地 EJB 无状态会话 Bean 来驱动事务时,你必须依赖于 EJB 容器和 JTA,即使你只访问一个数据库并且只使用无状态会话 Bean 来通过容器管理的事务提供声明式事务。以编程方式直接使用 JTA 也需要一个 Jakarta EE 环境。

Spring’s transaction support is not bound to a container. When configured with any strategy other than JTA, transaction support also works in a stand-alone or test environment. Especially in the typical case of single-database transactions, Spring’s single-resource local transaction support is a lightweight and powerful alternative to JTA. When you use local EJB stateless session beans to drive transactions, you depend both on an EJB container and on JTA, even if you access only a single database and use only stateless session beans to provide declarative transactions through container-managed transactions. Direct use of JTA programmatically also requires a Jakarta EE environment.

Spring 驱动的事务使用本地定义的 Hibernate SessionFactory 可以像使用本地 JDBC DataSource 一样工作,只要它们访问的是一个数据库。因此,只有在你具有分布式事务需求时,才需要使用 Spring 的 JTA 事务策略。JCA 连接器需要容器特定的部署步骤,并且(显然)首先需要 JCA 支持。与部署使用本地资源定义和 Spring 驱动的简单 Web 应用程序相比,这种配置需要更多的工作。

Spring-driven transactions can work as well with a locally defined Hibernate SessionFactory as they do with a local JDBC DataSource, provided they access a single database. Thus, you need only use Spring’s JTA transaction strategy when you have distributed transaction requirements. A JCA connector requires container-specific deployment steps, and (obviously) JCA support in the first place. This configuration requires more work than deploying a simple web application with local resource definitions and Spring-driven transactions.

综合考虑,如果你不使用 EJB,请坚持使用本地 SessionFactory 设置和 Spring 的 HibernateTransactionManagerJtaTransactionManager。你可以获得所有好处,包括适当的事务性 JVM 级缓存和分布式事务,而不用经历容器部署的不便。通过 JCA 连接器将 Hibernate SessionFactory 注册到 JNDI 只有在与 EJB 结合使用时才有价值。

All things considered, if you do not use EJBs, stick with local SessionFactory setup and Spring’s HibernateTransactionManager or JtaTransactionManager. You get all of the benefits, including proper transactional JVM-level caching and distributed transactions, without the inconvenience of container deployment. JNDI registration of a Hibernate SessionFactory through the JCA connector adds value only when used in conjunction with EJBs.

Spurious Application Server Warnings with Hibernate

在一些实现非常严格的 XADataSource 的 JTA 环境中(目前有一些 WebLogic 服务器和 WebSphere 版本),当 Hibernate 在不考虑该环境的 JTA 事务管理器的情况下配置时,应用程序服务器日志中可能出现虚假警告或异常。这些警告或异常表明正在访问的连接不再有效或 JDBC 访问不再有效,可能是因为事务不再处于活动状态。例如,以下是一个来自 WebLogic 的实际异常:

In some JTA environments with very strict XADataSource implementations (currently some WebLogic Server and WebSphere versions), when Hibernate is configured without regard to the JTA transaction manager for that environment, spurious warnings or exceptions can show up in the application server log. These warnings or exceptions indicate that the connection being accessed is no longer valid or JDBC access is no longer valid, possibly because the transaction is no longer active. As an example, here is an actual exception from WebLogic:

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

另一个常见的问题是在 JTA 事务之后发生连接泄漏,导致 Hibernate 会话(和潜在的底层 JDBC 连接)没有正确关闭。

Another common problem is a connection leak after JTA transactions, with Hibernate sessions (and potentially underlying JDBC connections) not getting closed properly.

你可以让 Hibernate 识别 JTA 事务管理器(与 Spring 同步)来解决此类问题。你可以通过两种方式来做到这一点:

You can resolve such issues by making Hibernate aware of the JTA transaction manager, to which it synchronizes (along with Spring). You have two options for doing this:

  • Pass your Spring JtaTransactionManager bean to your Hibernate setup. The easiest way is a bean reference into the jtaTransactionManager property for your LocalSessionFactoryBean bean (see Hibernate Transaction Setup). Spring then makes the corresponding JTA strategies available to Hibernate.

  • You may also configure Hibernate’s JTA-related properties explicitly, in particular "hibernate.transaction.coordinator_class", "hibernate.connection.handling_mode" and potentially "hibernate.transaction.jta.platform" in your "hibernateProperties" on LocalSessionFactoryBean (see Hibernate’s manual for details on those properties).

本节的其余部分描述了在 Hibernate 识别 JTA PlatformTransactionManager 的情况下和不识别的情况下发生的事件序列。

The remainder of this section describes the sequence of events that occur with and without Hibernate’s awareness of the JTA PlatformTransactionManager.

当 Hibernate 还没有配置为识别 JTA 事务管理器时,JTA 事务提交时会发生以下事件:

When Hibernate is not configured with any awareness of the JTA transaction manager, the following events occur when a JTA transaction commits:

  • The JTA transaction commits.

  • Spring’s JtaTransactionManager is synchronized to the JTA transaction, so it is called back through an afterCompletion callback by the JTA transaction manager.

  • Among other activities, this synchronization can trigger a callback by Spring to Hibernate, through Hibernate’s afterTransactionCompletion callback (used to clear the Hibernate cache), followed by an explicit close() call on the Hibernate session, which causes Hibernate to attempt to close() the JDBC Connection.

  • In some environments, this Connection.close() call then triggers the warning or error, as the application server no longer considers the Connection to be usable, because the transaction has already been committed.

当 Hibernate 已配置为识别 JTA 事务管理器时,JTA 事务提交时会发生以下事件:

When Hibernate is configured with awareness of the JTA transaction manager, the following events occur when a JTA transaction commits:

  • The JTA transaction is ready to commit.

  • Spring’s JtaTransactionManager is synchronized to the JTA transaction, so the transaction is called back through a beforeCompletion callback by the JTA transaction manager.

  • Spring is aware that Hibernate itself is synchronized to the JTA transaction and behaves differently than in the previous scenario. In particular, it aligns with Hibernate’s transactional resource management.

  • The JTA transaction commits.

  • Hibernate is synchronized to the JTA transaction, so the transaction is called back through an afterCompletion callback by the JTA transaction manager and can properly clear its cache.