我们首先在 Spring 环境中介绍 Hibernate 5, 使用它来演示 Spring 集成 OR 映射器所采用的方法。 本节详细介绍了许多问题,并展示了 DAO 的不同变体 实现和事务划分。这些模式中的大多数可以直接 转换为所有其他支持的 ORM 工具。然后,本章后面的章节 介绍其他 ORM 技术并显示简要示例。
从 Spring Framework 6.0 开始,Spring 需要 Hibernate ORM 5.5+ 用于 Spring 以及本机 Hibernate 设置。
我们建议将 Hibernate ORM 5.6 作为该 Hibernate 一代的最后一个功能分支。 Hibernate ORM 6.x 仅支持作为 JPA 提供程序 ()。
不再支持使用软件包进行普通设置。
对于新的开发项目,我们建议使用具有 JPA 样式设置的 Hibernate ORM 6.1/6.2。 |
从 Spring Framework 6.0 开始,Spring 需要 Hibernate ORM 5.5+ 用于 Spring 以及本机 Hibernate 设置。
我们建议将 Hibernate ORM 5.6 作为该 Hibernate 一代的最后一个功能分支。 Hibernate ORM 6.x 仅支持作为 JPA 提供程序 ()。
不再支持使用软件包进行普通设置。
对于新的开发项目,我们建议使用具有 JPA 样式设置的 Hibernate ORM 6.1/6.2。 |
SessionFactory
在 Spring 容器中设置
为避免将应用程序对象绑定到硬编码的资源查找,您可以定义
资源(例如 JDBC 或 Hibernate )作为 bean 中的
Spring 容器。需要访问资源的应用程序对象接收引用
通过 bean 引用传递给此类预定义实例,如 DAO 中所示
定义。DataSource
SessionFactory
以下摘自 XML 应用程序上下文定义的部分显示了如何设置
JDBC 和它之上的 Hibernate:DataSource
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 切换到位于 JNDI 的位置(通常由应用程序服务器管理)只是一个问题
配置,如下例所示:BasicDataSource
DataSource
<beans>
<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>
您还可以使用 Spring 的 / 访问位于 JNDI 的位置来检索和公开它。
但是,这在 EJB 上下文之外通常并不常见。SessionFactory
JndiObjectFactoryBean
<jee:jndi-lookup>
Spring 还提供了一个 variant,无缝集成
使用样式配置和编程设置(不涉及)。 两者和支持背景
引导,Hibernate 初始化与应用程序并行运行
给定 bootstrap 执行程序(如 a )上的 bootstrap 线程。
在 上,这可以通过属性获得。在 programmatic 上,有一个采用 bootstrap executor 参数的重载方法。 从 Spring Framework 5.1 开始,这种本机 Hibernate 设置还可以在本机 Hibernate 访问旁边公开用于标准 JPA 交互的 JPA。
有关详细信息,请参阅 JPA 的本机 Hibernate 设置。 |
Spring 还提供了一个 variant,无缝集成
使用样式配置和编程设置(不涉及)。 两者和支持背景
引导,Hibernate 初始化与应用程序并行运行
给定 bootstrap 执行程序(如 a )上的 bootstrap 线程。
在 上,这可以通过属性获得。在 programmatic 上,有一个采用 bootstrap executor 参数的重载方法。 从 Spring Framework 5.1 开始,这种本机 Hibernate 设置还可以在本机 Hibernate 访问旁边公开用于标准 JPA 交互的 JPA。
有关详细信息,请参阅 JPA 的本机 Hibernate 设置。 |
基于普通 Hibernate API 实现 DAO
Hibernate 有一个称为上下文会话的功能,其中 Hibernate 自己管理
每笔交易 1 个 current。这大致相当于 Spring 的
每个事务同步一个 Hibernate。相应的 DAO
实现类似于以下示例,基于普通的 Hibernate API:Session
Session
-
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 参考文档和示例的风格。
除了持有 in an instance 变量。我们强烈建议
这种基于实例的设置,而不是 old-school 类
Hibernate 的 CaveatEmptor 示例应用程序。(通常,除非绝对必要,否则不要在 variables 中保留任何资源。SessionFactory
static
HibernateUtil
static
前面的 DAO 示例遵循依赖关系注入模式。它非常适合 Spring IoC
container 的 .
您还可以在纯 Java 中设置这样的 DAO(例如,在单元测试中)。为此,
实例化它并使用所需的 Factory Reference.作为
Spring bean 定义,DAO 将类似于以下内容:HibernateTemplate
setSessionFactory(..)
<beans>
<bean id="myProductDao" class="product.ProductDaoImpl">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
</beans>
这种 DAO 风格的主要优点是它只依赖于 Hibernate API。无导入 的 Spring 类是必需的。这是非侵入性的吸引力 视角,并且对于 Hibernate 开发人员来说可能感觉更自然。
但是,DAO 会抛出 plain (这是未选中的,因此它没有
被声明或捕获),这意味着调用者只能将异常视为
通常是致命的 — 除非他们想依赖 Hibernate 自己的异常层次结构。
如果没有
将调用方绑定到 implementation strategy。这种权衡可能是可以接受的
强基于 Hibernate 的应用程序不需要任何特殊例外
治疗,或两者兼而有之。HibernateException
幸运的是,Spring 支持任何 Spring 事务策略的 Hibernate 方法,
返回当前 Spring 管理的 transactional ,即使使用 .该方法的标准行为保持不变
返回与正在进行的 JTA 事务关联的 current (如果有)。
无论您使用的是 Spring、EJB 容器管理事务 (CMT) 还是 JTA,此行为都适用。LocalSessionFactoryBean
SessionFactory.getCurrentSession()
Session
HibernateTransactionManager
Session
JtaTransactionManager
总之,您可以基于普通的 Hibernate API 实现 DAO,同时仍然保持 能够参与 Spring 管理的事务。
声明式事务划分
我们建议您使用 Spring 的声明式事务支持,它允许您 将 Java 代码中的显式事务划分 API 调用替换为 AOP 交易拦截器。您可以在 Spring 中配置此事务拦截器 container 结合使用。此声明式事务功能 让您保持业务服务没有重复的事务划分代码,并且 专注于添加业务逻辑,这是应用程序的真正价值。
在继续之前,我们强烈建议您阅读 Declarative Transaction Management(如果您还没有阅读)。 |
您可以使用注释注释来注释服务层,并指示
Spring 容器来查找这些注解并为
这些带注释的方法。以下示例显示了如何执行此操作:@Transactional
-
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()
}
在容器中,您需要设置实现
(作为 Bean)和一个条目,选择在运行时进行处理。以下示例显示了如何执行此操作:PlatformTransactionManager
<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>
在继续之前,我们强烈建议您阅读 Declarative Transaction Management(如果您还没有阅读)。 |
编程事务划分
您可以在应用程序的更高级别中划分事务,除了
跨任意数量的操作的较低级别数据访问服务。限制也没有
存在于周围业务服务的实现中。它只需要一个 Spring .同样,后者可以来自任何地方,但最好是
作为 bean 引用。此外,应该由方法设置。以下代码段对显示
Spring 应用程序上下文中的事务管理器和业务服务定义
以及一个业务方法实现的示例:PlatformTransactionManager
setTransactionManager(..)
productDAO
setProductDao(..)
<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 允许引发任何已检查的应用程序异常
替换为回调代码,while 限制为 un选中
exceptions 的 Exceptions 中。 在
未选中的应用程序异常,或者如果事务被标记为仅回滚
应用程序(通过设置 )。默认情况下,其行为方式相同,但允许每个方法的可配置回滚策略。TransactionInterceptor
TransactionTemplate
TransactionTemplate
TransactionStatus
TransactionInterceptor
事务管理策略
Both 和 delegate 实际事务
处理到实例(可以是(对于单个 Hibernate ),通过在后台使用 a)或 a(委托给
JTA 子系统)用于 Hibernate 应用程序。您甚至可以使用自定义实现。从本机 Hibernate 事务切换
management 到 JTA(例如,当面对某些
deployments)只是一个配置问题。您可以将
Hibernate 事务管理器与 Spring 的 JTA 事务实现。双
事务划分和数据访问代码无需更改即可工作,因为它们
使用通用事务管理 API。TransactionTemplate
TransactionInterceptor
PlatformTransactionManager
HibernateTransactionManager
SessionFactory
ThreadLocal
Session
JtaTransactionManager
PlatformTransactionManager
对于跨多个 Hibernate 会话工厂的分布式事务,你可以作为具有多个定义的事务策略进行组合。然后,每个 DAO 都会获得一个传递到其相应 bean 属性中的特定引用。如果所有底层 JDBC 数据
源是事务性容器源,业务服务可以划分事务
在任意数量的 DAO 和任意数量的会话工厂中,没有特别考虑,作为
只要它用作策略。JtaTransactionManager
LocalSessionFactoryBean
SessionFactory
JtaTransactionManager
两者兼而有之,并允许适当的
使用 Hibernate 进行 JVM 级缓存处理,无需特定于容器的事务管理器
lookup 或 JCA 连接器(如果您不使用 EJB 启动事务)。HibernateTransactionManager
JtaTransactionManager
HibernateTransactionManager
可以将 Hibernate JDBC 导出为普通 JDBC
特定 .此功能允许高级
混合 Hibernate 和 JDBC 数据访问的事务划分完全无需
JTA,前提是您只访问一个数据库。 自然而然
将 Hibernate 事务公开为 JDBC 事务,如果你已经使用 class 的 through 属性设置了传入的事务。或者,您可以通过类的属性显式指定应该公开事务的 the。Connection
DataSource
HibernateTransactionManager
SessionFactory
DataSource
dataSource
LocalSessionFactoryBean
DataSource
dataSource
HibernateTransactionManager
对于实际资源连接的 JTA 样式的惰性检索, Spring 提供了一个
目标连接池对应的代理类:参见 LazyConnectionDataSourceProxy
。
这对于 Hibernate 只读事务特别有用,因为 Hibernate 只读事务通常
从本地缓存进行处理,而不是命中数据库。DataSource
比较容器管理的资源和本地定义的资源
您可以在容器管理的 JNDI 和本地定义的 JNDI 之间切换
一个,而无需更改任何一行应用程序代码。是否保留
容器中或应用程序本地的资源定义主要是
与您使用的事务策略有关。与 Spring-defined local 相比,手动注册的 JNDI 不提供任何
好处。通过 Hibernate 的 JCA 连接器部署提供了
参与 Jakarta EE 服务器的管理基础设施的附加价值,但确实
不在此之后增加实际价值。SessionFactory
SessionFactory
SessionFactory
SessionFactory
Spring 的事务支持未绑定到容器。当配置了任何策略时 除了 JTA 之外,事务支持还可以在独立或测试环境中工作。 特别是在单数据库事务的典型情况下, Spring 的单资源 本地事务支持是 JTA 的轻量级且功能强大的替代方案。当您使用 本地 EJB 无状态会话 bean 来驱动事务,则您都依赖于 EJB 容器和 JTA 上,即使您只访问单个数据库并且仅使用无状态 会话 Bean 通过容器管理的 交易。以编程方式直接使用 JTA 还需要 Jakarta EE 环境。
Spring 驱动的事务可以与本地定义的 Hibernate 一起工作,就像它们与本地 JDBC 一样,前提是它们访问
单个数据库。因此,当您
有分布式事务需求。JCA 连接器需要特定于容器的
部署步骤,以及(显然)首先是 JCA 支持。此配置
比使用本地资源部署简单的 Web 应用程序需要更多的工作
定义和 Spring 驱动的事务。SessionFactory
DataSource
考虑到所有因素,如果您不使用 EJB,请坚持使用本地设置
和 Spring 的 或 .您将获得所有
好处,包括适当的事务性 JVM 级缓存和分布式
事务,没有容器部署的不便。JNDI 注册
通过 JCA 连接器进行的休眠仅在用于
与 EJB 结合使用。SessionFactory
HibernateTransactionManager
JtaTransactionManager
SessionFactory
使用 Hibernate 的虚假应用程序服务器警告
在一些具有非常严格实现的 JTA 环境中(目前
某些 WebLogic Server 和 WebSphere 版本),当 Hibernate 配置为没有
关于该环境的 JTA 事务管理器、虚假警告或
异常可以显示在 Application Server 日志中。这些警告或例外
指示正在访问的连接不再有效或 JDBC 访问为 NO
不再有效,可能是因为事务不再有效。例如,
以下是 WebLogic 的实际异常:XADataSource
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 bean 传递给 Hibernate 设置。最简单的 way 是 bean 属性的 bean 引用(参见 Hibernate Transaction Setup)。 然后,Spring 将相应的 JTA 策略提供给 Hibernate。
JtaTransactionManager
jtaTransactionManager
LocalSessionFactoryBean
-
你也可以显式地配置 Hibernate 的 JTA 相关属性,特别是 “hibernate.transaction.coordinator_class”、“hibernate.connection.handling_mode” 以及 “hibernateProperties” 中可能出现的 “hibernate.transaction.jta.platform” on(有关这些属性的详细信息,请参见 Hibernate 的手册)。
LocalSessionFactoryBean
本节的其余部分描述了使用 和 发生的事件序列
没有 Hibernate 对 JTA 的认知。PlatformTransactionManager
当 Hibernate 未配置任何 JTA 事务管理器时, 提交 JTA 事务时,将发生以下事件:
-
JTA 事务提交。
-
Spring 的与 JTA 事务同步,因此它是 通过 JTA 事务管理器的回调回调。
JtaTransactionManager
afterCompletion
-
在其他活动中,此同步可以触发 Spring 对 Hibernate,通过 Hibernate 的回调(用于清除 Hibernate 缓存),然后对 Hibernate 会话进行显式调用, 这会导致 Hibernate 尝试 JDBC Connection。
afterTransactionCompletion
close()
close()
-
在某些环境中,此调用会触发警告或 错误,因为应用程序服务器不再认为 可用, ,因为事务已经提交。
Connection.close()
Connection
当 Hibernate 配置了 JTA 事务管理器时, 提交 JTA 事务时,将发生以下事件:
-
JTA 事务已准备好提交。
-
Spring 的 与 JTA 事务同步,因此 transaction 通过 JTA 的回调回调 事务管理器。
JtaTransactionManager
beforeCompletion
-
Spring 知道 Hibernate 本身与 JTA 事务同步,并且 的行为与上一个场景中的行为不同。特别是,它与 Hibernate 的事务性资源管理。
-
JTA 事务提交。
-
Hibernate 与 JTA 事务同步,因此该事务被回调 通过 JTA 事务管理器的回调,并且可以 正确清除其缓存。
afterCompletion