此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
Hibernate
我们首先在 Spring 环境中介绍 Hibernate 5, 使用它来演示 Spring 集成 OR 映射器所采用的方法。 本节详细介绍了许多问题,并展示了 DAO 的不同变体 实现和事务划分。这些模式中的大多数可以直接 转换为所有其他支持的 ORM 工具。然后,本章后面的章节 介绍其他 ORM 技术并显示简要示例。
从 Spring Framework 6.0 开始,Spring 需要 Spring 的 Hibernate ORM 5.5+ Hibernate ORM 6.x 仅支持作为 JPA 提供程序 ( |
SessionFactory
在 Spring 容器中设置
为避免将应用程序对象绑定到硬编码的资源查找,您可以定义
资源(例如 JDBCDataSource
或 HibernateSessionFactory
) 作为 bean
Spring 容器。需要访问资源的应用程序对象接收引用
通过 bean 引用传递给此类预定义实例,如 DAO 中所示
定义。
以下摘自 XML 应用程序上下文定义的部分显示了如何设置
JDBCDataSource
和一个 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.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>
您还可以访问位于 JNDI 的SessionFactory
,使用 Spring 的JndiObjectFactoryBean
/ <jee:jndi-lookup>
以检索和公开它。
但是,这在 EJB 上下文之外通常并不常见。
Spring 还提供了一个 双 这种本机 Hibernate 设置还可以公开 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
在实例变量中。我们强烈建议
这种基于实例的设置优于老式static
HibernateUtil
class from
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。无导入 的 Spring 类是必需的。这是非侵入性的吸引力 视角,并且对于 Hibernate 开发人员来说可能感觉更自然。
然而,DAO 抛出了HibernateException
(未选中,因此它没有
被声明或捕获),这意味着调用者只能将异常视为
通常是致命的 — 除非他们想依赖 Hibernate 自己的异常层次结构。
如果没有
将调用方绑定到 implementation strategy。这种权衡可能是可以接受的
强基于 Hibernate 的应用程序不需要任何特殊例外
治疗,或两者兼而有之。
幸运的是,Spring 的LocalSessionFactoryBean
支持 Hibernate 的SessionFactory.getCurrentSession()
方法,
返回当前 Spring 管理的事务Session
,即使使用HibernateTransactionManager
.该方法的标准行为保持不变
返回当前的Session
与正在进行的 JTA 交易相关联(如果有)。
无论您是否使用 Spring 的JtaTransactionManager
、EJB 容器管理事务 (CMT) 或 JTA。
总之,您可以基于普通的 Hibernate API 实现 DAO,同时仍然保持 能够参与 Spring 管理的事务。
声明式事务划分
我们建议您使用 Spring 的声明式事务支持,它允许您 将 Java 代码中的显式事务划分 API 调用替换为 AOP 交易拦截器。您可以在 Spring 中配置此事务拦截器 container 结合使用。此声明式事务功能 让您保持业务服务没有重复的事务划分代码,并且 专注于添加业务逻辑,这是应用程序的真正价值。
在继续之前,我们强烈建议您阅读 Declarative Transaction Management(如果您还没有阅读)。 |
您可以使用@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/>
entry, 选择加入@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>
编程事务划分
您可以在应用程序的更高级别中划分事务,除了
跨任意数量的作的较低级别数据访问服务。限制也没有
存在于周围业务服务的实现中。它只需要一个 SpringPlatformTransactionManager
.同样,后者可以来自任何地方,但最好是
作为 Bean 引用,通过setTransactionManager(..)
方法。此外,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
限制为未选中
exceptions 的 Exceptions 中。TransactionTemplate
在
未选中的应用程序异常,或者如果事务被标记为仅回滚
应用程序(通过设置TransactionStatus
).默认情况下,TransactionInterceptor
的行为方式相同,但允许每个方法配置回滚策略。
事务管理策略
双TransactionTemplate
和TransactionInterceptor
委托实际交易
handling 设置为PlatformTransactionManager
实例(可以是HibernateTransactionManager
(对于单个 HibernateSessionFactory
) 通过使用ThreadLocal
Session
Under the Hood) 或JtaTransactionManager
(委托给
JTA 子系统)用于 Hibernate 应用程序。您甚至可以使用自定义PlatformTransactionManager
实现。从本机 Hibernate 事务切换
management 到 JTA(例如,当面对某些
deployments)只是一个配置问题。您可以将
Hibernate 事务管理器与 Spring 的 JTA 事务实现。双
事务划分和数据访问代码无需更改即可工作,因为它们
使用通用事务管理 API。
对于跨多个 Hibernate 会话工厂的分布式事务,你可以将JtaTransactionManager
作为具有多个LocalSessionFactoryBean
定义。然后,每个 DAO 都会获得一个特定的SessionFactory
引用传递到其相应的 bean 属性中。如果所有底层 JDBC 数据
源是事务性容器源,业务服务可以划分事务
在任意数量的 DAO 和任意数量的会话工厂中,没有特别考虑,作为
只要它使用JtaTransactionManager
作为策略。
双HibernateTransactionManager
和JtaTransactionManager
允许适当的
使用 Hibernate 进行 JVM 级缓存处理,无需特定于容器的事务管理器
lookup 或 JCA 连接器(如果您不使用 EJB 启动事务)。
HibernateTransactionManager
可以导出 Hibernate JDBCConnection
转换为普通 JDBC
特定DataSource
.此功能允许高级
混合 Hibernate 和 JDBC 数据访问的事务划分完全无需
JTA,前提是您只访问一个数据库。HibernateTransactionManager
自然而然
将 Hibernate 事务作为 JDBC 事务公开(如果您已设置传入的SessionFactory
替换为DataSource
通过dataSource
属性的LocalSessionFactoryBean
类。或者,您可以显式指定DataSource
为此,事务应该通过dataSource
属性的HibernateTransactionManager
类。
对于实际资源连接的 JTA 样式的惰性检索, Spring 提供了一个
相应DataSource
Proxy 类:请参阅LazyConnectionDataSourceProxy
.
这对于 Hibernate 只读事务特别有用,因为 Hibernate 只读事务通常
从本地缓存进行处理,而不是命中数据库。
比较容器管理的资源和本地定义的资源
您可以在容器管理的 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
,前提是他们访问
单个数据库。因此,当您
有分布式事务需求。JCA 连接器需要特定于容器的
部署步骤,以及(显然)首先是 JCA 支持。此配置
比使用本地资源部署简单的 Web 应用程序需要更多的工作
定义和 Spring 驱动的事务。
考虑到所有因素,如果您不使用 EJB,请坚持使用本地SessionFactory
设置
和 Spring 的HibernateTransactionManager
或JtaTransactionManager
.您将获得所有
好处,包括适当的事务性 JVM 级缓存和分布式
事务,没有容器部署的不便。JNDI 注册
HibernateSessionFactory
通过 JCA 连接器仅在用于
与 EJB 结合使用。
使用 Hibernate 的虚假应用程序服务器警告
在一些 JTA 环境中,具有非常严格的XADataSource
implementations(当前
某些 WebLogic Server 和 WebSphere 版本),当 Hibernate 配置为没有
关于该环境的 JTA 事务管理器、虚假警告或
异常可以显示在 Application Server 日志中。这些警告或例外
指示正在访问的连接不再有效或 JDBC 访问为 NO
不再有效,可能是因为事务不再有效。例如,
以下是 WebLogic 的实际异常:
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
另一个常见的问题是 JTA 事务后的连接泄漏,使用 Hibernate sessions(以及可能的基础 JDBC 连接)未正确关闭。
您可以通过让 Hibernate 识别 JTA 事务管理器来解决此类问题。 它与它同步(与 Spring 一起)。您有两种选择来执行此作:
-
传递你的Spring
JtaTransactionManager
bean 添加到您的 Hibernate 设置中。最简单的 way 是 bean 引用到jtaTransactionManager
属性LocalSessionFactoryBean
bean(请参见Hibernate Transaction Setup)。 然后,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
callback 的调用。 -
在其他活动中,此同步可以触发 Spring 对 Hibernate,通过 Hibernate 的
afterTransactionCompletion
callback(用于清除 Hibernate 缓存),后跟一个显式的close()
在 Hibernate 会话上调用 这会导致 Hibernate 尝试close()
JDBC 连接。 -
在某些环境中,此
Connection.close()
call 然后触发警告或 错误,因为应用程序服务器不再考虑Connection
可用, ,因为事务已经提交。
当 Hibernate 配置了 JTA 事务管理器时, 提交 JTA 事务时,将发生以下事件:
-
JTA 事务已准备好提交。
-
Spring的
JtaTransactionManager
同步到 JTA 事务,因此 事务通过beforeCompletion
由 JTA 回调 事务管理器。 -
Spring 知道 Hibernate 本身与 JTA 事务同步,并且 的行为与上一个场景中的行为不同。特别是,它与 Hibernate 的事务性资源管理。
-
JTA 事务提交。
-
Hibernate 与 JTA 事务同步,因此该事务被回调 通过
afterCompletion
callback 和 can。 正确清除其缓存。