此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

Hibernate

我们从对 Spring 环境中的 Hibernate 的介绍开始, 使用它来演示 Spring 集成 OR 映射器所采取的方法。 本节详细介绍了许多问题,并展示了 DAO 的不同变体 实现和交易划分。这些模式中的大多数可以直接 转换为所有其他受支持的 ORM 工具。本章的后面部分则 介绍其他 ORM 技术并展示简短示例。spring-doc.cadn.net.cn

从 Spring Framework 7.0 开始,Spring 需要 Hibernate ORM 7.x 才能使用 Spring 的HibernateJpaVendorAdapter.spring-doc.cadn.net.cn

org.springframework.orm.jpa.hibernate包取代了前者orm.hibernate5: 现在可以与 Hibernate ORM 7.1+ 一起使用,与HibernateJpaVendorAdapter以及支持 Hibernate 的原生SessionFactory.getCurrentSession()风格。spring-doc.cadn.net.cn

SessionFactory在 Spring 容器中设置

为避免将应用程序对象绑定到硬编码资源查找,您可以定义 资源(例如 JDBCDataSource或 HibernateSessionFactory) 作为 bean 中的 bean 弹簧容器。需要访问资源的应用程序对象接收引用 通过 bean 引用到此类预定义实例,如 DAO 中所示 定义spring-doc.cadn.net.cn

以下摘录自 XML 应用程序上下文定义,演示了如何设置 JDBC 公司DataSource和 HibernateSessionFactory在它之上:spring-doc.cadn.net.cn

<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(通常由应用程序服务器管理)只是 配置,如以下示例所示:spring-doc.cadn.net.cn

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

您还可以访问位于 JNDI 的SessionFactory,使用 Spring 的JndiObjectFactoryBean / <jee:jndi-lookup>检索并公开它。 但是,这在 EJB 上下文之外通常并不常见。spring-doc.cadn.net.cn

Spring 还提供了一个LocalSessionFactoryBuilder可变,无缝集成 跟@Bean样式配置和编程设置(无FactoryBean涉及)。spring-doc.cadn.net.cn

LocalSessionFactoryBeanLocalSessionFactoryBuilder支持背景 引导,Hibernate 初始化与应用程序并行运行 给定 Bootstrap 执行器上的 Bootstrap 线程(例如SimpleAsyncTaskExecutor). 上LocalSessionFactoryBean,这可以通过bootstrapExecutor财产。在程序化LocalSessionFactoryBuilder,则有过载的buildSessionFactory方法,该方法采用引导执行器参数。spring-doc.cadn.net.cn

这样的原生 Hibernate 设置也可以公开一个 JPAEntityManagerFactory对于标准 JPA 交互。 有关详细信息,请参阅 JPA 的本机休眠设置spring-doc.cadn.net.cn

基于 Plain Hibernate API 实现 DAO

Hibernate 有一个称为上下文会话的功能,其中 Hibernate 本身管理 一个电流Session每笔交易。这大致相当于 Spring 的 一个休眠的同步Session每笔交易。对应的 DAO 实现类似于以下示例,基于纯 Hibernate API:spring-doc.cadn.net.cn

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变量,除非绝对必要。spring-doc.cadn.net.cn

前面的 DAO 示例遵循依赖注入模式。它非常适合 Spring IoC 容器,就像针对 Spring 的HibernateTemplate. 您还可以在纯 Java 中设置这样的 DAO(例如,在单元测试中)。为此, 实例化它并调用setSessionFactory(..)与所需的工厂参考。作为 Spring bean 定义,DAO 将类似于以下内容:spring-doc.cadn.net.cn

<beans>

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

</beans>

这种 DAO 风格的主要优点是它仅依赖于 Hibernate API。无导入 任何春季课程的课程都是必需的。这从非侵入性中很有吸引力 视角,对于 Hibernate 开发人员来说可能感觉更自然。spring-doc.cadn.net.cn

然而,DAO 抛出的HibernateException(未选中,因此它没有 被声明或捕获),这意味着调用者只能将异常视为 通常是致命的——除非他们想依赖 Hibernate 自己的异常层次结构。 如果没有特定原因,就不可能捕获特定原因(例如乐观锁定失败) 将调用方与实施策略绑定。这种权衡可能是可以接受的 基于休眠的应用程序不需要任何特殊例外 治疗,或两者兼而有之。spring-doc.cadn.net.cn

幸运的是,Spring的LocalSessionFactoryBean支持 Hibernate 的SessionFactory.getCurrentSession()任何 Spring 事务策略的方法, 返回当前 Spring 管理的事务Session,即使有HibernateTransactionManager.该方法的标准行为仍然存在 返回当前Session与正在进行的 JTA 事务(如果有)相关联。 无论您是否使用 Spring 的JtaTransactionManager、EJB 容器管理事务 (CMT) 或 JTA。spring-doc.cadn.net.cn

综上所述,您可以基于纯 Hibernate API 实现 DAO,同时仍然 能够参与 Spring 管理的交易。spring-doc.cadn.net.cn

声明性事务分界

我们建议您使用 Spring 的声明式事务支持,它允许您 将 Java 代码中的显式事务分界 API 调用替换为 AOP 事务拦截器。您可以在 Spring 中配置此事务拦截器 容器,使用 Java 注释或 XML。这种声明式事务功能 让您保持业务服务不受重复交易分界代码的影响,并且 专注于添加业务逻辑,这是应用程序的真正价值。spring-doc.cadn.net.cn

在继续之前,我们强烈建议您阅读声明式事务管理(如果您还没有阅读过)。

您可以使用@Transactional注释并指示 Spring 容器来查找这些注释并为 这些带注释的方法。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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运行时处理。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

<?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 应用程序上下文中的事务管理器和业务服务定义 以及业务方法实现的示例:spring-doc.cadn.net.cn

<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>
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-doc.cadn.net.cn

事务管理策略

TransactionTemplateTransactionInterceptor委托实际交易 handling 到PlatformTransactionManager实例(可以是HibernateTransactionManager(对于单个休眠SessionFactory) 通过使用ThreadLocal Session在引擎盖下)或JtaTransactionManager(委托给 JTA 子系统)用于 Hibernate 应用程序。您甚至可以使用自定义PlatformTransactionManager实现。从本机 Hibernate 事务切换 管理到 JTA (例如在面对某些分布式事务需求时) 应用程序的部署)只是配置问题。您可以将 Hibernate 事务管理器与 Spring 的 JTA 事务实现。双 事务分界和数据访问代码无需更改即可工作,因为它们 使用通用事务管理 API。spring-doc.cadn.net.cn

对于跨多个 Hibernate 会话工厂的分布式事务,您可以将JtaTransactionManager作为具有多个LocalSessionFactoryBean定义。然后每个 DAO 都会获得一个特定的SessionFactory引用传递到其相应的 bean 属性中。如果所有基础 JDBC 数据 源是事务容器,业务服务可以划分事务 跨任意数量的 DAO 和任意数量的会话工厂,无需特别考虑,如 只要它使用JtaTransactionManager作为战略。spring-doc.cadn.net.cn

HibernateTransactionManagerJtaTransactionManager允许适当的 使用 Hibernate 进行 JVM 级缓存处理,无需特定于容器的事务管理器 查找或 JCA 连接器(如果您不使用 EJB 来启动事务)。spring-doc.cadn.net.cn

HibernateTransactionManager可以导出 Hibernate JDBCConnection到普通 JDBC 特定DataSource.这种能力允许高水平 混合 Hibernate 和 JDBC 数据访问的事务划分完全没有 JTA,前提是您仅访问一个数据库。HibernateTransactionManager自然而然 如果您已设置传入的SessionFactory使用DataSource通过dataSource属性的LocalSessionFactoryBean类。或者,您可以显式指定DataSource事务应该通过dataSource属性的HibernateTransactionManager类。spring-doc.cadn.net.cn

对于 JTA 风格的实际资源连接的延迟检索,Spring 提供了一个 相应DataSource目标连接池的代理类:请参阅LazyConnectionDataSourceProxy. 这对于休眠只读事务特别有用,因为通常可以 从本地缓存处理,而不是访问数据库。spring-doc.cadn.net.cn

比较容器管理的资源和本地定义的资源

您可以在容器管理的 JNDI 之间切换SessionFactory和本地定义的 无需更改任何一行应用程序代码。是否保留 容器中或应用程序本地的资源定义主要是 您使用的交易策略的问题。与 Spring 定义的本地相比SessionFactory,手动注册的 JNDISessionFactory不提供任何 好处。部署SessionFactory通过 Hibernate 的 JCA 连接器提供了 参与 Jakarta EE 服务器的管理基础设施的附加值,但确实如此 不增加超出此范围的实际价值。spring-doc.cadn.net.cn

Spring 的事务支持不绑定到容器。配置任何策略时 除了 JTA 之外,事务支持还可以在独立或测试环境中工作。 特别是在单库事务的典型情况下,Spring 的单资源 本地事务支持是 JTA 的轻量级且功能强大的替代方案。当您使用 本地 EJB 无状态会话 Bean 来驱动事务,则两者都依赖于 EJB 容器和 JTA 上,即使您只访问单个数据库并且仅使用无状态 会话 bean 通过容器管理提供声明性事务 交易。以编程方式直接使用 JTA 还需要 Jakarta EE 环境。spring-doc.cadn.net.cn

Spring驱动的事务也可以与本地定义的Hibernate一起使用SessionFactory就像他们对本地 JDBC 所做的那样DataSource,前提是他们访问 单个数据库。因此,您只需在以下情况下使用 Spring 的 JTA 事务策略 有分布式事务要求。JCA 连接器需要特定于容器的 部署步骤,以及(显然)首先的 JCA 支持。此配置 与使用本地资源部署简单的 Web 应用程序相比,需要更多的工作 定义和 Spring 驱动的事务。spring-doc.cadn.net.cn

考虑到所有因素,如果您不使用 EJB,请坚持使用本地SessionFactory设置 和 Spring 的HibernateTransactionManagerJtaTransactionManager.你得到所有的 好处,包括适当的事务性 JVM 级缓存和分布式 事务,没有容器部署的不便。JNDI 注册 HibernateSessionFactory通过 JCA 连接器仅在用于 与 EJB 结合。spring-doc.cadn.net.cn

Hibernate 的虚假应用程序服务器警告

在一些非常严格的 JTA 环境中XADataSource实现(目前 某些 WebLogic Server 和 WebSphere 版本),当 Hibernate 配置为 关于该环境的 JTA 事务管理器、虚假警告或 异常可以显示在应用程序服务器日志中。这些警告或异常 指示正在访问的连接不再有效或 JDBC 访问权限为“否” 更有效,可能是因为交易不再活跃。例如, 这是 WebLogic 的一个实际例外:spring-doc.cadn.net.cn

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

另一个常见问题是 JTA 事务后的连接泄漏,使用 Hibernate 会话(以及潜在的底层 JDBC 连接)未正确关闭。spring-doc.cadn.net.cn

您可以通过让 Hibernate 了解 JTA 事务管理器 它与之同步(与 Spring 一起)。您有两个选项可以执行此作:spring-doc.cadn.net.cn

  • 通过你的SpringJtaTransactionManagerbean 添加到您的 Hibernate 设置中。最简单的 way 是对jtaTransactionManager为您的财产LocalSessionFactoryBeanbean(参见 Hibernate 事务设置)。 然后,Spring 将相应的 JTA 策略提供给 Hibernate。spring-doc.cadn.net.cn

  • 您还可以显式配置 Hibernate 的 JTA 相关属性,特别是 “hibernate.transaction.coordinator_class”、“hibernate.connection.handling_mode” 以及可能的“hibernateProperties”中的“hibernate.transaction.jta.platform” 上LocalSessionFactoryBean(有关这些属性的详细信息,请参阅 Hibernate 的手册)。spring-doc.cadn.net.cn

本节的其余部分描述了 和 发生的事件序列 没有 Hibernate 对 JTA 的认识PlatformTransactionManager.spring-doc.cadn.net.cn

当 Hibernate 未配置任何 JTA 事务管理器感知时, 当 JTA 事务提交时,会发生以下事件:spring-doc.cadn.net.cn

  • JTA 事务提交。spring-doc.cadn.net.cn

  • Spring的JtaTransactionManager与 JTA 事务同步,因此它是 通过afterCompletionJTA 事务管理器的回调。spring-doc.cadn.net.cn

  • 在其他活动中,这种同步可以触发 Spring 的回调 Hibernate,通过 Hibernate 的afterTransactionCompletion回调(用于清除 Hibernate 缓存),后跟显式close()调用 Hibernate 会话, 这会导致 Hibernate 尝试close()JDBC 连接。spring-doc.cadn.net.cn

  • 在某些环境中,此Connection.close()调用,然后触发警告或 错误,因为应用程序服务器不再考虑Connection可用, 因为事务已经提交。spring-doc.cadn.net.cn

当 Hibernate 配置为 JTA 事务管理器的感知时, 当 JTA 事务提交时,会发生以下事件:spring-doc.cadn.net.cn

  • JTA 事务已准备好提交。spring-doc.cadn.net.cn

  • Spring的JtaTransactionManager同步到 JTA 事务,因此 transaction 通过beforeCompletionJTA 回调 事务管理器。spring-doc.cadn.net.cn

  • Spring 知道 Hibernate 本身与 JTA 事务同步,并且 的行为与前一个方案不同。特别是,它与 Hibernate 的事务性资源管理。spring-doc.cadn.net.cn

  • JTA 事务提交。spring-doc.cadn.net.cn

  • Hibernate 与 JTA 事务同步,因此该事务被回调 通过afterCompletionJTA 事务管理器的回调,并且可以 正确清除其缓存。spring-doc.cadn.net.cn