此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
Hibernate
我们从对 Spring 环境中的 Hibernate 的介绍开始, 使用它来演示 Spring 集成 OR 映射器所采取的方法。 本节详细介绍了许多问题,并展示了 DAO 的不同变体 实现和交易划分。这些模式中的大多数可以直接 转换为所有其他受支持的 ORM 工具。本章的后面部分则 介绍其他 ORM 技术并展示简短示例。
从 Spring Framework 7.0 开始,Spring 需要 Hibernate ORM 7.x 才能使用 Spring 的 这 |
SessionFactory
在 Spring 容器中设置
为避免将应用程序对象绑定到硬编码资源查找,您可以定义
资源(例如 JDBCDataSource
或 HibernateSessionFactory
) 作为 bean 中的 bean
弹簧容器。需要访问资源的应用程序对象接收引用
通过 bean 引用到此类预定义实例,如 DAO 中所示
定义。
以下摘录自 XML 应用程序上下文定义,演示了如何设置
JDBC 公司DataSource
和 HibernateSessionFactory
在它之上:
<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.jpa.hibernate.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>
您还可以访问位于 JNDI 的SessionFactory
,使用 Spring 的JndiObjectFactoryBean
/ <jee:jndi-lookup>
检索并公开它。
但是,这在 EJB 上下文之外通常并不常见。
Spring 还提供了一个 双 这样的原生 Hibernate 设置也可以公开一个 JPA |
基于 Plain Hibernate API 实现 DAO
Hibernate 有一个称为上下文会话的功能,其中 Hibernate 本身管理
一个电流Session
每笔交易。这大致相当于 Spring 的
一个休眠的同步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
在实例变量中。我们强烈推荐
这种基于实例的设置优于老式static
HibernateUtil
类从
Hibernate 的 CaveatEmptor 示例应用程序。(一般情况下,不要将任何资源保留在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。无导入 任何春季课程的课程都是必需的。这从非侵入性中很有吸引力 视角,对于 Hibernate 开发人员来说可能感觉更自然。
然而,DAO 抛出的HibernateException
(未选中,因此它没有
被声明或捕获),这意味着调用者只能将异常视为
通常是致命的——除非他们想依赖 Hibernate 自己的异常层次结构。
如果没有特定原因,就不可能捕获特定原因(例如乐观锁定失败)
将调用方与实施策略绑定。这种权衡可能是可以接受的
基于休眠的应用程序不需要任何特殊例外
治疗,或两者兼而有之。
幸运的是,Spring的LocalSessionFactoryBean
支持 Hibernate 的SessionFactory.getCurrentSession()
任何 Spring 事务策略的方法,
返回当前 Spring 管理的事务Session
,即使有HibernateTransactionManager
.该方法的标准行为仍然存在
返回当前Session
与正在进行的 JTA 事务(如果有)相关联。
无论您是否使用 Spring 的JtaTransactionManager
、EJB 容器管理事务 (CMT) 或 JTA。
综上所述,您可以基于纯 Hibernate API 实现 DAO,同时仍然 能够参与 Spring 管理的交易。
声明性事务分界
我们建议您使用 Spring 的声明式事务支持,它允许您 将 Java 代码中的显式事务分界 API 调用替换为 AOP 事务拦截器。您可以在 Spring 中配置此事务拦截器 容器,使用 Java 注释或 XML。这种声明式事务功能 让您保持业务服务不受重复交易分界代码的影响,并且 专注于添加业务逻辑,这是应用程序的真正价值。
在继续之前,我们强烈建议您阅读声明式事务管理(如果您还没有阅读过)。 |
您可以使用@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.jpa.hibernate.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven/>
<bean id="myProductService" class="product.SimpleProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
程序化交易分界
您可以在应用程序的更高级别中划分事务,在
跨越任意数量作的较低级别的数据访问服务。限制也没有
存在于周边业务服务的实施上。它只需要一个弹簧PlatformTransactionManager
.同样,后者可以来自任何地方,但最好是
作为 bean 引用,通过setTransactionManager(..)
方法。此外,productDAO
应由setProductDao(..)
方法。以下一对片段显示
Spring 应用程序上下文中的事务管理器和业务服务定义
以及业务方法实现的示例:
<beans>
<bean id="myTxManager" class="org.springframework.orm.jpa.hibernate.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
委托实际交易
handling 到PlatformTransactionManager
实例(可以是HibernateTransactionManager
(对于单个休眠SessionFactory
) 通过使用ThreadLocal
Session
在引擎盖下)或JtaTransactionManager
(委托给
JTA 子系统)用于 Hibernate 应用程序。您甚至可以使用自定义PlatformTransactionManager
实现。从本机 Hibernate 事务切换
管理到 JTA (例如在面对某些分布式事务需求时)
应用程序的部署)只是配置问题。您可以将
Hibernate 事务管理器与 Spring 的 JTA 事务实现。双
事务分界和数据访问代码无需更改即可工作,因为它们
使用通用事务管理 API。
对于跨多个 Hibernate 会话工厂的分布式事务,您可以将JtaTransactionManager
作为具有多个LocalSessionFactoryBean
定义。然后每个 DAO 都会获得一个特定的SessionFactory
引用传递到其相应的 bean 属性中。如果所有基础 JDBC 数据
源是事务容器,业务服务可以划分事务
跨任意数量的 DAO 和任意数量的会话工厂,无需特别考虑,如
只要它使用JtaTransactionManager
作为战略。
双HibernateTransactionManager
和JtaTransactionManager
允许适当的
使用 Hibernate 进行 JVM 级缓存处理,无需特定于容器的事务管理器
查找或 JCA 连接器(如果您不使用 EJB 来启动事务)。
HibernateTransactionManager
可以导出 Hibernate JDBCConnection
到普通 JDBC
特定DataSource
.这种能力允许高水平
混合 Hibernate 和 JDBC 数据访问的事务划分完全没有
JTA,前提是您仅访问一个数据库。HibernateTransactionManager
自然而然
如果您已设置传入的SessionFactory
使用DataSource
通过dataSource
属性的LocalSessionFactoryBean
类。或者,您可以显式指定DataSource
事务应该通过dataSource
属性的HibernateTransactionManager
类。
对于 JTA 风格的实际资源连接的延迟检索,Spring 提供了一个
相应DataSource
目标连接池的代理类:请参阅LazyConnectionDataSourceProxy
.
这对于休眠只读事务特别有用,因为通常可以
从本地缓存处理,而不是访问数据库。
比较容器管理的资源和本地定义的资源
您可以在容器管理的 JNDI 之间切换SessionFactory
和本地定义的
无需更改任何一行应用程序代码。是否保留
容器中或应用程序本地的资源定义主要是
您使用的交易策略的问题。与 Spring 定义的本地相比SessionFactory
,手动注册的 JNDISessionFactory
不提供任何
好处。部署SessionFactory
通过 Hibernate 的 JCA 连接器提供了
参与 Jakarta EE 服务器的管理基础设施的附加值,但确实如此
不增加超出此范围的实际价值。
Spring 的事务支持不绑定到容器。配置任何策略时 除了 JTA 之外,事务支持还可以在独立或测试环境中工作。 特别是在单库事务的典型情况下,Spring 的单资源 本地事务支持是 JTA 的轻量级且功能强大的替代方案。当您使用 本地 EJB 无状态会话 Bean 来驱动事务,则两者都依赖于 EJB 容器和 JTA 上,即使您只访问单个数据库并且仅使用无状态 会话 bean 通过容器管理提供声明性事务 交易。以编程方式直接使用 JTA 还需要 Jakarta EE 环境。
Spring驱动的事务也可以与本地定义的Hibernate一起使用SessionFactory
就像他们对本地 JDBC 所做的那样DataSource
,前提是他们访问
单个数据库。因此,您只需在以下情况下使用 Spring 的 JTA 事务策略
有分布式事务要求。JCA 连接器需要特定于容器的
部署步骤,以及(显然)首先的 JCA 支持。此配置
与使用本地资源部署简单的 Web 应用程序相比,需要更多的工作
定义和 Spring 驱动的事务。
考虑到所有因素,如果您不使用 EJB,请坚持使用本地SessionFactory
设置
和 Spring 的HibernateTransactionManager
或JtaTransactionManager
.你得到所有的
好处,包括适当的事务性 JVM 级缓存和分布式
事务,没有容器部署的不便。JNDI 注册
HibernateSessionFactory
通过 JCA 连接器仅在用于
与 EJB 结合。
Hibernate 的虚假应用程序服务器警告
在一些非常严格的 JTA 环境中XADataSource
实现(目前
某些 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 事务管理器 它与之同步(与 Spring 一起)。您有两个选项可以执行此作:
-
通过你的Spring
JtaTransactionManager
bean 添加到您的 Hibernate 设置中。最简单的 way 是对jtaTransactionManager
为您的财产LocalSessionFactoryBean
bean(参见 Hibernate 事务设置)。 然后,Spring 将相应的 JTA 策略提供给 Hibernate。 -
您还可以显式配置 Hibernate 的 JTA 相关属性,特别是 “hibernate.transaction.coordinator_class”、“hibernate.connection.handling_mode” 以及可能的“hibernateProperties”中的“hibernate.transaction.jta.platform” 上
LocalSessionFactoryBean
(有关这些属性的详细信息,请参阅 Hibernate 的手册)。
本节的其余部分描述了 和 发生的事件序列
没有 Hibernate 对 JTA 的认识PlatformTransactionManager
.
当 Hibernate 未配置任何 JTA 事务管理器感知时, 当 JTA 事务提交时,会发生以下事件:
-
JTA 事务提交。
-
Spring的
JtaTransactionManager
与 JTA 事务同步,因此它是 通过afterCompletion
JTA 事务管理器的回调。 -
在其他活动中,这种同步可以触发 Spring 的回调 Hibernate,通过 Hibernate 的
afterTransactionCompletion
回调(用于清除 Hibernate 缓存),后跟显式close()
调用 Hibernate 会话, 这会导致 Hibernate 尝试close()
JDBC 连接。 -
在某些环境中,此
Connection.close()
调用,然后触发警告或 错误,因为应用程序服务器不再考虑Connection
可用, 因为事务已经提交。
当 Hibernate 配置为 JTA 事务管理器的感知时, 当 JTA 事务提交时,会发生以下事件:
-
JTA 事务已准备好提交。
-
Spring的
JtaTransactionManager
同步到 JTA 事务,因此 transaction 通过beforeCompletion
JTA 回调 事务管理器。 -
Spring 知道 Hibernate 本身与 JTA 事务同步,并且 的行为与前一个方案不同。特别是,它与 Hibernate 的事务性资源管理。
-
JTA 事务提交。
-
Hibernate 与 JTA 事务同步,因此该事务被回调 通过
afterCompletion
JTA 事务管理器的回调,并且可以 正确清除其缓存。