|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
Hibernate
我们首先介绍在Spring环境中的
|
自Spring Framework 6.0起,Spring要求Hibernate ORM 5.5+版本以支持Spring的 Hibernate ORM 6.x 仅支持作为 JPA 提供程序 ( |
SessionFactory 在Spring容器中的设置
为了避免将应用程序对象与硬编码的资源查找绑定,可以将资源(例如一个JDBC DataSource或一个Hibernate SessionFactory)定义为Spring容器中的bean。需要访问资源的应用程序对象可以通过bean引用获得这些预定义实例的引用,如下一节中的DAO定义所示。
以下是从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 的 SessionFactory / JndiObjectFactoryBean 来访问一个 JNDI 位置的 SessionFactory,并获取和暴露它。
不过,这在 EJB 上下文之外通常不常见。
|
Spring 还提供了一个
从 Spring Framework 5.1 开始,这种原生 Hibernate 设置也可以在原生 Hibernate 访问之外,为标准 JPA 交互提供一个 JPA
|
基于原生Hibernate API实现DAO
Hibernate 有一个名为上下文会话的功能,其中 Hibernate 本身会在每个事务中管理一个当前的 Session。这大致相当于 Spring 每个事务中对 Hibernate Session 的同步。相应的 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()
}
}
这种样式与Hibernate参考文档和示例类似,
只是将SessionFactory保存在实例变量中。我们强烈建议使用这种基于实例的设置,而不是Hibernate的CaveatEmptor示例应用程序中的旧式static HibernateUtil类。(一般来说,除非绝对必要,否则不要将任何资源保存在static变量中。)
前面的DAO示例遵循依赖注入模式。它非常适合放入Spring IoC容器中,就像使用Spring的HibernateTemplate编码时一样。
你也可以在纯Java中设置这样的DAO(例如,在单元测试中)。要做到这一点,
实例化它并调用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 容器管理的事务(CMTs)还是 JTA,此行为都适用。
总之,你可以基于普通的 Hibernate API 实现 DAO,同时仍然可以参与 Spring 管理的事务。
声明式事务界定
我们建议您使用 Spring 的声明式事务支持,这可以让您用 AOP 事务拦截器替换 Java 代码中的显式事务分界 API 调用。您可以通过使用 Java 注解或 XML 在 Spring 容器中配置此事务拦截器。这种声明式事务功能可以让您保持业务服务不包含重复的事务分界代码,并专注于添加业务逻辑,这才是您应用程序的实际价值。
| 在继续之前,我们强烈建议您阅读 声明式事务管理 如果您还没有这样做的话。 |
您可以使用<code>0</code>注解对服务层进行注解,并指示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 实例(这可以是一个 HibernateTransactionManager(用于单个 Hibernate SessionFactory)通过在后台使用一个 ThreadLocal Session)或者一个 JtaTransactionManager(将事务委托给容器的 JTA 子系统)用于 Hibernate 应用程序。你甚至可以使用自定义的 PlatformTransactionManager 实现。从原生 Hibernate 事务管理切换到 JTA(例如当你的应用程序某些部署需要分布式事务时)只需进行配置即可。你可以将 Hibernate 事务管理器替换为 Spring 的 JTA 事务实现。事务界定和数据访问代码无需更改,因为它们使用的是通用的事务管理 API。
对于跨多个Hibernate会话工厂的分布式事务,可以将JtaTransactionManager作为事务策略与多个LocalSessionFactoryBean定义结合使用。每个DAO然后会获得一个特定的SessionFactory引用,传递到其对应的bean属性中。如果所有底层JDBC数据源都是事务容器数据源,业务服务可以在不特别注意的情况下,使用JtaTransactionManager作为策略,在任何数量的DAO和任何数量的会话工厂之间标记事务。
HibernateTransactionManager 和 JtaTransactionManager 都可以在不使用容器特定的事务管理器查找或 JCA 连接器(如果你不使用 EJB 启动事务)的情况下,实现与 Hibernate 的 JVM 级缓存处理。
HibernateTransactionManager 可以将 Hibernate JDBC Connection 导出为特定 DataSource 的普通 JDBC 访问代码。此功能允许在完全不使用 JTA 的情况下,通过混合使用 Hibernate 和 JDBC 数据访问进行高级事务声明。前提是您只访问一个数据库。如果已通过 dataSource 属性将传入的 SessionFactory 与 DataSource 进行了设置,HibernateTransactionManager 会自动将 Hibernate 事务作为 JDBC 事务公开。或者,您可以通过 HibernateTransactionManager 类的 dataSource 属性显式指定要公开事务的 DataSource。
容器管理与本地定义资源对比
您可以在容器管理的JNDI SessionFactory和本地定义的JNDI之间切换,而无需更改任何一行应用程序代码。是将资源定义保留在容器中还是本地应用程序内,主要取决于您使用的事务策略。相比Spring定义的本地SessionFactory,手动注册的JNDISessionFactory并无任何优势。通过Hibernate的JCA连接器部署SessionFactory可提供参与Jakarta EE服务器管理基础设施的附加值,但除此之外并未带来实际价值。
Spring的事务支持并不依赖于容器。当配置为除JTA之外的任何策略时,事务支持在独立环境或测试环境中同样有效。尤其在典型的单数据库事务场景中,Spring的单资源本地事务支持是JTA的轻量级且功能强大的替代方案。若使用本地EJB无状态会话Bean驱动事务,则需同时依赖EJB容器和JTA——即便仅访问单个数据库且仅通过容器管理事务的无状态会话Bean提供声明式事务。以编程方式直接使用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 Server 和 WebSphere 版本),当 Hibernate 配置时未考虑该环境的 JTA 事务管理器,应用程序服务器日志中可能会出现虚假的警告或异常。这些警告或异常表明正在访问的连接已无效,或者 JDBC 访问已无效,可能是因为事务不再处于活动状态。例如,这是来自 WebLogic 的一个实际异常:
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
另一个常见问题是 JTA 事务后出现的连接泄漏,Hibernate 会话(以及可能的底层 JDBC 连接)未能正确关闭。
你可以通过让 Hibernate 了解 JTA 事务管理器来解决此类问题,Hibernate 会与该事务管理器(以及 Spring)进行同步。为此你有两种选择:
-
将您的 Spring
JtaTransactionManagerbean 传递给您的 Hibernate 设置。最简单的方法是将一个 bean 引用到您的jtaTransactionManager属性中的LocalSessionFactoryBeanbean(参见 Hibernate 事务设置)。 Spring 然后会向 Hibernate 提供相应的 JTA 策略。 -
您也可以显式配置Hibernate的JTA相关属性,特别是“hibernate.transaction.coordinator_class”、“hibernate.connection.handling_mode”以及可能的“hibernate.transaction.jta.platform”,在“hibernateProperties”中设置为“0”(有关这些属性的详细信息,请参阅Hibernate的手册)。
本节的其余部分描述了在Hibernate了解JTA PlatformTransactionManager和不了解JTA PlatformTransactionManager的情况下发生的事件顺序。
当 Hibernate 没有配置对 JTA 事务管理器的任何了解时,当 JTA 事务提交时会发生以下事件:
-
JTA 事务提交。
-
Spring的
JtaTransactionManager与JTA事务同步,因此通过JTA事务管理器的afterCompletion回调被调用。 -
除了其他活动外,此同步可以通过 Spring 触发对 Hibernate 的回调, 通过 Hibernate 的
afterTransactionCompletion回调(用于清除 Hibernate 缓存), 然后是对 Hibernate 会话的显式close()调用, 这会导致 Hibernate 尝试close()JDBC 连接。 -
在某些环境中,此
Connection.close()调用会触发警告或错误,因为应用程序服务器不再认为Connection是可用的,因为事务已经提交。
当 Hibernate 配置为了解 JTA 事务管理器时, 在 JTA 事务提交时会发生以下事件:
-
JTA事务已准备好提交。
-
Spring 的
JtaTransactionManager与 JTA 事务同步,因此事务通过 JTA 事务管理器的beforeCompletion回调被调用。 -
Spring 知道 Hibernate 本身已与 JTA 事务同步,并且其行为与之前的场景不同。特别是,它与 Hibernate 的事务资源管理保持一致。
-
JTA 事务提交。
-
Hibernate 与 JTA 事务同步,因此事务会通过
afterCompletion回调由 JTA 事务管理器调用,并可以正确清除其缓存。