Spring Framework:对象关系映射(ORM)数据访问

https://docs.spring.io/spring-framework/reference/data-access/orm.html

Spring中的ORM简介

Spring框架支持与Java持久化API(JPA)集成,并支持原生Hibernate进行资源管理、数据访问对象(DAO)实现和事务策略。例如,对于Hibernate,有一流的支持,包括多个方便的IoC特性,解决了许多典型的Hibernate集成问题。可以通过依赖注入配置所有支持的OR(对象关系)映射工具的功能。它们可以参与Spring的资源和事务管理,并遵守Spring的通用事务和DAO异常层次结构。推荐集成方式是针对简单的Hibernate或JPA API编写DAO代码。

在创建数据访问应用程序时,Spring会显著增强你选择的ORM层。可以根据需要利用尽可能多的集成支持,并且应该将这种集成努力与在内部构建类似基础设施的成本和风险进行比较。可以像使用库一样使用大部分ORM支持,而不考虑技术,因为一切都是设计为一组可重用的JavaBeans。在Spring IoC容器中的ORM便于配置和部署。因此,本节中的大多数示例展示了Spring容器内的配置。

使用Spring框架创建ORM DAO的好处包括:

  • 更容易测试。Spring的IoC方法使得替换Hibernate SessionFactory实例、JDBC DataSource实例、事务管理器和映射对象实现(如果需要)的实现和配置位置变得容易。这反过来又使得更容易独立地测试每一块与持久性相关的代码。
  • 通用数据访问异常。Spring可以包装来自ORM工具的异常,将它们从专有(可能是受检)异常转换为通用运行时DataAccessException层次结构。这个特性允许你只在适当的层处理大多数不可恢复的持久化异常,无需烦人的模板代码catchthrow和异常声明。你仍然可以根据需要捕获和处理异常。JDBC异常(包括特定于数据库的方言)也转换为相同的层次结构,这意味着可以在一致的编程模型中使用JDBC执行一些操作。
  • 通用资源管理。Spring应用程序上下文可以处理Hibernate SessionFactory实例、JPA EntityManagerFactory实例、JDBC DataSource实例以及其他相关资源的位置和配置。这使得这些值易于管理和更改。Spring提供高效、简便且安全的持久化资源处理。例如,使用Hibernate的相关代码通常需要使用相同的Hibernate Session以确保效率和正确的事务处理。Spring通过Hibernate SessionFactory暴露当前Session,可以轻松地创建并将Session绑定到当前线程透明地。因此,Spring解决了典型Hibernate使用中的许多长期问题,适用于任何本地或JTA事务环境。
  • 集成事务管理。可以通过@Transactional注解或在XML配置文件中显式配置事务AOP通知(advice),使用声明式、面向切面编程(AOP)风格的方法拦截器来包装你的ORM代码。在这两种情况下,事务语义和异常处理(回滚等)都由你来处理。还可以更换各种事务管理器,而不影响ORM相关代码。例如,可以在本地事务和JTA之间切换,两种情况下都有相同的完整服务(如声明式事务)。此外,与JDBC相关的代码可以与用于进行ORM的代码完全集成事务。这对于不适合ORM的数据访问(如批处理和BLOB流)但仍然需要与ORM操作共享公共事务的情况很有用。

通用ORM集成考虑因素

本节强调适用于所有ORM技术的考虑因素。

Spring的ORM集成的主要目标是清晰的应用程序分层(适用于任何数据访问和事务技术)以及应用程序对象的松散耦合——不再有业务服务对数据访问或事务策略的依赖,不再有硬编码的资源查找,不再难以替换的单例,不再有自定义的服务注册表。目标是有一种简单一致的方法来连接应用程序对象,保持它们的可重用性,并尽可能避免容器依赖。所有单独的数据访问特性都可以独立使用,但与Spring的应用程序上下文概念很好地集成,提供基于XML的配置和对不需要Spring感知的普通JavaBean实例的交叉引用。在一个典型的Spring应用程序中,许多重要的对象都是JavaBeans:数据访问模板、数据访问对象、事务管理器、使用数据访问对象和事务管理器的业务服务、Web视图解析器、使用业务服务的Web控制器等。

资源和事务管理

Spring倡导简单有效的资源处理方法,即在JDBC的情况下通过模板进行IoC,在ORM技术中应用AOP拦截器。

基础设施提供了适当的资源处理和将特定API异常转换为非检查的基础设施异常层次结构。Spring引入了适用于任何数据访问策略的DAO异常层次结构。

在事务管理方面,JdbcTemplate类与Spring事务支持相集成,并通过相应的Spring事务管理器支持JTA(Java事务API)和JDBC事务。对于支持的ORM(对象关系映射)技术,Spring通过Hibernate和JPA(Java持久化API)事务管理器以及JTA支持提供Hibernate和JPA支持。

异常转换(Exception Translation)

当在DAO中使用Hibernate或JPA时,必须决定如何处理持久化技术的本地异常类。DAO抛出的是一个HibernateExceptionPersistenceException的子类,具体取决于技术。这些异常都是运行时异常,不必声明或捕获。可能还需要处理IllegalArgumentExceptionIllegalStateException。这意味着调用者只能将异常视为通常是致命的,除非他们想依赖于持久化技术自己的异常结构。没有将调用者绑定到实现策略的情况下,捕获特定原因(如乐观锁定失败)是不可能的。这种权衡对于强烈基于ORM的应用程序或者不需要任何特殊异常处理(或两者兼有)的应用程序来说可能是可以接受的。然而,Spring允许通过@Repository注解透明地应用异常转换。以下示例(一个用于Java配置,另一个用于XML配置)展示了如何做到这一点:

@Repository
public class ProductDaoImpl implements ProductDao {

	// class body here...

}
<beans>

	<!-- Exception translation bean post processor -->
	<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

	<bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

后处理器(postprocessor )自动寻找所有异常转换器(PersistenceExceptionTranslator接口的实现),并通知所有使用@Repository注解标记的bean,以便发现的转换器可以拦截并对抛出的异常应用适当的转换。

总之,可以基于简单的持久化技术的API和注解实现DAO,同时仍然从Spring管理的事务、依赖注入和透明异常转换(如果需要)中受益,转换为Spring的自定义异常层次结构。

Hibernate

我们从在Spring环境中使用Hibernate 5开始,用它来展示Spring集成OR映射器的方法。本节详细讨论了许多问题,并展示了DAO实现和事务划分的不同变体。这些模式中的大多数可以直接转换到所有其他支持的ORM工具。

截至Spring框架6.0,Spring要求Hibernate ORM 5.5+用于Spring的HibernateJpaVendorAdapter以及本地Hibernate SessionFactory设置。推荐使用Hibernate ORM 5.6作为该版本Hibernate的最后一个功能分支。

Hibernate ORM 6.x仅作为JPA提供者(HibernateJpaVendorAdapter)得到支持。不再支持使用orm.hibernate5包的普通SessionFactory设置。推荐在新的开发项目中使用Hibernate ORM 6.1/6.2与JPA风格的设置。

在Spring容器中设置SessionFactory

为了避免将应用程序对象与硬编码的资源查找绑定,可以在Spring容器中将资源(如JDBC DataSource或Hibernate SessionFactory)定义为bean。需要访问资源的应用程序对象通过bean引用接收对这些预定义实例的引用。

以下摘自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的JndiObjectFactoryBean / <jee:jndi-lookup>访问JNDI定位的SessionFactory,以检索并暴露它。然而,这在EJB上下文之外通常不常见。

Spring还提供了一个LocalSessionFactoryBuilder变体,与@Bean风格的配置和编程式设置(不涉及FactoryBean)无缝集成。

LocalSessionFactoryBeanLocalSessionFactoryBuilder都支持后台引导,Hibernate初始化在给定的引导执行器(如SimpleAsyncTaskExecutor)上与应用程序引导线程并行运行。在LocalSessionFactoryBean上,这可以通过bootstrapExecutor属性获得。在编程式的LocalSessionFactoryBuilder上,有一个重载的buildSessionFactory方法,它接受一个引导执行器参数。

截至Spring框架5.1,这种原生Hibernate设置也可以暴露一个JPA EntityManagerFactory,用于标准JPA交互以及原生Hibernate访问。

基于原生Hibernate API实现DAO

Hibernate有一个称为上下文会话的特性,其中Hibernate本身管理每个事务的一个当前Session。这大致相当于Spring的同步每个事务一个Hibernate Session。相应的DAO实现类似于以下示例,基于原生Hibernate API:

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();
	}
}

这种风格类似于Hibernate参考文档和示例中的风格,除了在实例变量中持有SessionFactory。推荐这种基于实例的设置,而不是来自Hibernate的CaveatEmptor示例应用程序的老派静态HibernateUtil类。(通常情况下,除非绝对必要,否则不要在任何静态变量中保留任何资源)

前面的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事务策略,即使在使用HibernateTransactionManager的情况下,也返回当前由Spring管理的事务性Session。如果没有正在进行的JTA事务,该方法的标准行为仍然是返回与当前的Session相关联的JTA事务。无论你是使用Spring的JtaTransactionManager、EJB容器管理的事务(CMTs)还是JTA,这种行为都适用。

总之,可以基于纯Hibernate API实现DAOs,同时仍然能够参与由Spring管理的事务。

声明式事务界定(Declarative Transaction Demarcation)

建议使用Spring的声明式事务支持,它允许用AOP事务拦截器替换Java代码中的显式事务划分API调用。可以在Spring容器中通过使用Java注解或XML配置此事务拦截器。这种声明式事务功能使你的业务服务不受重复的事务划分代码的影响,并专注于添加业务逻辑,这是应用程序的真正价值所在。

可以使用@Transactional注解标注服务层,并指示Spring容器查找这些注解并为这些带注解的方法提供事务性语义。以下示例展示了如何做到这一点:

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();
	}
}

在容器中,需要设置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>
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...
			}
		});
	}
}

Spring的TransactionInterceptor允许任何已检查的应用程序异常与回调代码一起抛出,而TransactionTemplate仅限于在回调中未检查的异常。如果发生未检查的应用程序异常或者应用程序通过设置TransactionStatus将事务标记为仅回滚,TransactionTemplate会触发回滚。默认情况下,TransactionInterceptor的行为相同,但允许为每个方法配置可定制的回滚策略。

事务管理策略

TransactionTemplateTransactionInterceptor都将实际的事务处理委托给一个PlatformTransactionManager实例(它可以是一个HibernateTransactionManager(用于单个Hibernate SessionFactory),在底层使用ThreadLocal Session)或一个JtaTransactionManager(委托给容器的JTA子系统)用于Hibernate应用程序。甚至可以使用自定义的PlatformTransactionManager实现。从原生Hibernate事务管理切换到JTA(例如,当应用程序的某些部署面临分布式事务要求时)只是配置问题。可以用Spring的JTA事务实现替换Hibernate事务管理器。事务划分和数据访问代码无需更改,因为它们使用的是通用的事务管理API。

对于跨越多个Hibernate会话工厂的分布式事务,可以将JtaTransactionManager作为事务策略与多个LocalSessionFactoryBean定义相结合。然后,每个DAO都会得到一个特定的SessionFactory引用,并将其传递给相应的bean属性。如果所有底层的JDBC数据源都是事务性容器数据源,那么只要业务服务使用JtaTransactionManager作为策略,它就可以在任何数量的DAO和任何数量的会话工厂之间界定事务,而无需特别关注。

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

HibernateTransactionManager可以将Hibernate的JDBC连接导出到特定数据源的普通JDBC访问代码。这种能力允许在完全不使用JTA的情况下进行混合Hibernate和JDBC数据访问的高级事务划分,前提是只访问一个数据库。如果已经通过LocalSessionFactoryBean类的dataSource属性为传入的SessionFactory设置了DataSourceHibernateTransactionManager会自动将Hibernate事务暴露为JDBC事务。或者,可以通过HibernateTransactionManager类的dataSource属性明确指定应该暴露事务的DataSource

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

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

可以在容器管理的JNDI SessionFactory和本地定义的SessionFactory之间切换,而无需更改任何应用程序代码。是否将资源定义保留在容器中还是在应用程序内部本地化,主要是使用的事务策略的问题。与Spring定义的本地SessionFactory相比,手动注册的JNDI SessionFactory没有任何优势。通过Hibernate的JCA连接器部署SessionFactory提供了参与Jakarta EE服务器管理基础设施的附加价值,但除此之外并没有增加实际价值。

Spring的事务支持并不绑定于任何容器。当配置使用除JTA之外的任何策略时,事务支持也适用于独立或测试环境。尤其是在典型的单一数据库事务场景中,Spring的单资源本地事务支持是一种轻量级且强大的替代方案,可以替代JTA。当使用本地EJB无状态会话bean来驱动事务时,即使只访问一个数据库并且仅使用无状态会话bean通过容器管理的事务提供声明式事务,也依赖于EJB容器和JTA。直接以编程方式使用JTA也需要Jakarta EE环境。

Spring驱动的事务可以与本地定义的Hibernate SessionFactory一起工作,就像它们与本地JDBC DataSource一样,前提是它们访问的是单一数据库。因此,只需要在有分布式事务要求时使用Spring的JTA事务策略。JCA连接器需要特定于容器的部署步骤,并且(显然)首先需要JCA支持。这种配置比部署具有本地资源定义和Spring驱动事务的简单web应用程序需要更多的工作。

总而言之,如果不使用EJB,那么坚持使用本地SessionFactory设置和Spring的HibernateTransactionManagerJtaTransactionManager。将获得所有好处,包括适当的事务性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.

另一个常见问题是在使用Hibernate会话(可能是底层的JDBC连接)后,JTA事务出现连接泄漏,没有正确关闭。

可以通过让Hibernate意识到JTA事务管理器(与Spring一起同步)来解决这些问题。为此,有两个选项:
将Spring JtaTransactionManager bean传递给Hibernate设置。最简单的方法是将bean引用传递到LocalSessionFactoryBean bean的jtaTransactionManager属性中。然后,Spring会向Hibernate提供相应的JTA策略。

还可以在LocalSessionFactoryBean上的“hibernateProperties”中显式配置Hibernate的JTA相关属性,特别是“hibernate.transaction.coordinator_class”、“hibernate.connection.handling_mode”和可能的“hibernate.transaction.jta.platform”。

当Hibernate没有配置对JTA事务管理器的任何意识时,以下事件将在JTA事务提交时发生:

  • JTA事务提交。
  • Spring的JtaTransactionManager与JTA事务同步,因此通过afterCompletion回调被JTA事务管理器调用回来。
  • 在其它活动中,这种同步可以触发Spring通过Hibernate的afterTransactionCompletion回调(用于清除Hibernate缓存)对Hibernate的回调,然后是对Hibernate会话的显式close()调用,这会导致Hibernate尝试关闭JDBC连接。
  • 在某些环境中,这个Connection.close()调用随后会触发警告或错误,因为应用服务器不再认为连接是可用的,因为事务已经提交。

当Hibernate配置了对JTA事务管理器的意识时,以下事件将在JTA事务提交时发生:

  • JTA事务准备提交。
  • Spring的JtaTransactionManager与JTA事务同步,因此事务通过beforeCompletion回调被JTA事务管理器调用回来。
  • Spring意识到Hibernate本身与JTA事务同步,并且与前面的情况有所不同。特别是,它与Hibernate的事务资源管理保持一致。
  • JTA事务提交。
  • Hibernate与JTA事务同步,因此事务通过afterCompletion回调被JTA事务管理器调用回来,并可以正确清除其缓存。

JPA

Spring JPA,位于org.springframework.orm.jpa包下,为Java持久化API提供了全面的支持,其方式与集成Hibernate类似,同时它还了解底层实现以便提供额外的功能。

在Spring环境中设置JPA的三种选项

Spring JPA支持提供了三种设置应用程序用来获取实体管理器的JPA EntityManagerFactory的方式:

  • 使用LocalEntityManagerFactoryBean
  • 从JNDI获取EntityManagerFactory
  • 使用LocalContainerEntityManagerFactoryBean

使用LocalEntityManagerFactoryBean

只能在简单的部署环境中使用此选项,例如独立应用程序和集成测试。

LocalEntityManagerFactoryBean创建一个适用于简单部署环境的EntityManagerFactory,在这种环境中应用程序仅使用JPA进行数据访问。该工厂bean使用JPA PersistenceProvider的自动检测机制(根据JPA的Java SE引导),在大多数情况下,只需指定持久化单元名称。以下XML示例配置了这样一个bean:

<beans>
	<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="myPersistenceUnit"/>
	</bean>
</beans>

这种形式的JPA部署是最简单和最受限的。不能引用现有的JDBC DataSource bean定义,也不存在对全局事务的支持。此外,持久化类的编织(字节码转换)是特定于提供者的,通常需要在启动时指定特定的JVM代理。这个选项只适用于独立应用程序和测试环境,这是JPA规范的设计目标。

从JNDI获取EntityManagerFactory

在部署到Jakarta EE服务器时,可以使用此选项。请查阅服务器的文档,了解如何将自定义JPA提供程序部署到你的服务器中,以便使用与服务器默认提供程序不同的提供程序。

从JNDI获取EntityManagerFactory(例如在Jakarta EE环境中),只需更改XML配置即可,如下例所示:

<beans>
	<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>

此操作假定使用标准的Jakarta EE引导。Jakarta EE服务器自动检测持久化单元(实际上,应用程序jar中的META-INF/persistence.xml文件)和Jakarta EE部署描述符(例如web.xml)中的persistence-unit-ref条目,并为这些持久化单元定义环境命名上下文位置。

在这种情况下,整个持久化单元的部署,包括持久化类的编织(字节码转换),都由Jakarta EE服务器负责。JDBC DataSource通过META-INF/persistence.xml文件中的JNDI位置定义。EntityManager事务与服务器的JTA子系统集成。Spring只是使用获得的EntityManagerFactory,并通过依赖注入将其传递给应用程序对象,同时管理持久化单元的事务(通常通过JtaTransactionManager)。

如果在同一应用程序中使用多个持久化单元,这些通过JNDI检索到的持久化单元的bean名称应与应用程序引用它们的持久化单元名称相匹配(例如,在@PersistenceUnit@PersistenceContext注解中)。

使用LocalContainerEntityManagerFactoryBean

可以在基于Spring的应用程序环境中使用此选项来获得完整的JPA功能。这包括像Tomcat这样的Web容器、独立应用程序,以及具有复杂持久化需求的集成测试。

LocalContainerEntityManagerFactoryBean允许完全控制EntityManagerFactory的配置,适用于需要细粒度自定义的环境。LocalContainerEntityManagerFactoryBean基于persistence.xml文件、提供的dataSourceLookup策略和指定的loadTimeWeaver创建一个PersistenceUnitInfo实例。因此,可以使用JNDI之外的自定义数据源,并控制编织过程。以下示例显示了LocalContainerEntityManagerFactoryBean的典型bean定义:

<beans>
	<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="someDataSource"/>
		<property name="loadTimeWeaver">
			<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
		</property>
	</bean>
</beans>

以下示例显示了一个典型的persistence.xml文件:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
	<persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
		<mapping-file>META-INF/orm.xml</mapping-file>
		<exclude-unlisted-classes/>
	</persistence-unit>
</persistence>

快捷方式<exclude-unlisted-classes/>表示不应该发生对注解实体类的扫描。显式的’true’值(<exclude-unlisted-classes>true</exclude-unlisted-classes/>)也表示不进行扫描。<exclude-unlisted-classes>false</exclude-unlisted-classes/>会触发扫描。然而,如果希望发生实体类扫描,建议省略exclude-unlisted-classes元素。

使用LocalContainerEntityManagerFactoryBean是功能最强大的JPA设置选项,允许在应用程序中进行灵活的本地配置。它支持链接到现有的JDBC DataSource,同时支持本地和全局事务等。然而,它也对运行时环境提出了要求,例如如果持久化提供者需要字节码转换,就需要一个能够进行编织的类加载器。

这个选项可能与Jakarta EE服务器的内置JPA功能发生冲突。在完整的Jakarta EE环境中,考虑从JNDI获取EntityManagerFactory。或者,在LocalContainerEntityManagerFactoryBean定义上指定一个自定义的persistenceXmlLocation(例如,META-INF/my-persistence.xml),并在应用程序jar文件中只包含具有该名称的描述符。因为Jakarta EE服务器只会查找默认的META-INF/persistence.xml文件,它会忽略这些自定义持久化单元,从而避免与Spring驱动的JPA设置预先发生冲突。(例如,这适用于Resin 3.1)

何时需要加载时编织?
并非所有JPA提供者都需要JVM代理。Hibernate就是不需要的一个示例。如果提供者不需要代理,或者有其他替代方案,例如通过自定义编译器或Ant任务在构建时应用增强,那么不应该使用加载时编织器。

LoadTimeWeaver接口是Spring提供的类,它允许JPA ClassTransformer实例以特定方式插入,这取决于环境是Web容器还是应用服务器。通过代理钩入ClassTransformer通常不太高效。代理针对整个虚拟机工作,并检查加载的每个类,这在生产服务器环境中通常是不期望的。

Spring为各种环境提供了多个LoadTimeWeaver实现,使得ClassTransformer实例仅应用于每个类加载器,而不是每个虚拟机。

可以使用@EnableLoadTimeWeaving注解或context:load-time-weaver XML元素来配置上下文范围的LoadTimeWeaver。这样的全局编织器会被所有JPA LocalContainerEntityManagerFactoryBean实例自动捕获。以下示例显示了设置加载时编织器的推荐方式,提供平台自动检测(例如,Tomcat的可编织类加载器或Spring的JVM代理)以及将编织器自动传播到所有支持编织的beans:

<context:load-time-weaver/>

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	...
</bean>

但是,如果需要,可以通过loadTimeWeaver属性手动指定一个专用的编织器,如下例所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="loadTimeWeaver">
		<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
	</property>
</bean>

无论LTW如何配置,使用这种技术,依赖插桩(或监控代码)的JPA应用程序都可以在目标平台(例如Tomcat)上运行,而不需要代理。当托管应用程序依赖于不同的JPA实现时,这一点尤其重要,因为JPA转换器仅在类加载器级别应用,因此彼此隔离。

处理多个持久化单元

对于依赖多个持久化单元位置的应用程序(例如,存储在类路径中的各种JARS中),Spring提供了PersistenceUnitManager来充当中央仓库,并避免了可能代价高昂的持久化单元发现过程。默认实现允许指定多个位置。这些位置被解析,稍后通过持久化单元名称检索。(默认情况下,搜索类路径中的META-INF/persistence.xml文件。)以下示例配置了多个位置:

<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
	<property name="persistenceXmlLocations">
		<list>
			<value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
			<value>classpath:/my/package/**/custom-persistence.xml</value>
			<value>classpath*:META-INF/persistence.xml</value>
		</list>
	</property>
	<property name="dataSources">
		<map>
			<entry key="localDataSource" value-ref="local-db"/>
			<entry key="remoteDataSource" value-ref="remote-db"/>
		</map>
	</property>
	<!-- if no datasource is specified, use this one -->
	<property name="defaultDataSource" ref="remoteDataSource"/>
</bean>

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="persistenceUnitManager" ref="pum"/>
	<property name="persistenceUnitName" value="myCustomUnit"/>
</bean>

默认实现允许在将PersistenceUnitInfo实例提供给JPA提供者之前对其进行自定义,可以通过声明性方式(通过其属性,影响所有托管单元)或编程方式(通过PersistenceUnitPostProcessor,允许选择持久化单元)。如果没有指定PersistenceUnitManagerLocalContainerEntityManagerFactoryBean将创建一个并在内部使用。

后台引导

LocalContainerEntityManagerFactoryBean 通过 bootstrapExecutor 属性支持后台引导,如下所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="bootstrapExecutor">
		<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
	</property>
</bean>

实际的JPA提供者引导被交给指定的执行器,然后并行运行于应用程序引导线程。暴露的EntityManagerFactory代理可以被注入到其他应用程序组件中,并且甚至能够响应EntityManagerFactoryInfo配置检查。然而,一旦其他组件访问实际的JPA提供者(例如,调用createEntityManager),这些调用会阻塞,直到后台引导完成。特别是在使用Spring Data JPA时,确保为其仓库设置延迟引导。

基于JPA实现DAO:EntityManagerFactory和EntityManager

虽然EntityManagerFactory实例是线程安全的,但EntityManager实例不是。注入的JPA EntityManager的行为就像从应用服务器的JNDI环境获取的EntityManager一样,如JPA规范所定义。如果有当前事务性的EntityManager,它会委托所有调用给该EntityManager。否则,它会回退到为每个操作新创建的EntityManager,实际上使其使用变得线程安全。

通过使用注入的EntityManagerFactoryEntityManager,可以编写不依赖任何Spring依赖项的纯JPA代码。如果启用了PersistenceAnnotationBeanPostProcessor,Spring可以理解字段和方法级别的@PersistenceUnit@PersistenceContext注解。以下示例显示了一个使用@PersistenceUnit注解的纯JPA DAO实现:

public class ProductDaoImpl implements ProductDao {

	private EntityManagerFactory emf;

	@PersistenceUnit
	public void setEntityManagerFactory(EntityManagerFactory emf) {
		this.emf = emf;
	}

	public Collection loadProductsByCategory(String category) {
		EntityManager em = this.emf.createEntityManager();
		try {
			Query query = em.createQuery("from Product as p where p.category = ?1");
			query.setParameter(1, category);
			return query.getResultList();
		}
		finally {
			if (em != null) {
				em.close();
			}
		}
	}
}

前面的DAO没有依赖Spring,仍然可以很好地适应Spring应用程序上下文。此外,DAO利用注解来要求注入默认的EntityManagerFactory,如下例bean定义所示:

<beans>

	<!-- bean post-processor for JPA annotations -->
	<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

	<bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

作为明确定义PersistenceAnnotationBeanPostProcessor的替代方法,可以考虑在应用程序上下文配置中使用Spring的context:annotation-config XML元素。这样做会自动注册所有基于注解配置的Spring标准后处理器,包括CommonAnnotationBeanPostProcessor等。

考虑以下示例:

<beans>

	<!-- post-processors for all standard config annotations -->
	<context:annotation-config/>

	<bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

这样的DAO的主要问题是它总是通过工厂创建一个新的EntityManager。可以通过请求注入一个事务性的EntityManager(也称为“共享EntityManager”,因为它是实际事务性EntityManager的共享、线程安全的代理)来避免这种情况。以下示例展示了如何做到这一点:

public class ProductDaoImpl implements ProductDao {

	@PersistenceContext
	private EntityManager em;

	public Collection loadProductsByCategory(String category) {
		Query query = em.createQuery("from Product as p where p.category = :category");
		query.setParameter("category", category);
		return query.getResultList();
	}
}

@PersistenceContext注解有一个可选的属性type,默认值为PersistenceContextType.TRANSACTION。可以使用这个默认值来接收一个共享的EntityManager代理。另一种选择,PersistenceContextType.EXTENDED,则是完全不同的事情。这会导致所谓的扩展EntityManager,它不是线程安全的,因此不能在并发访问的组件中使用,比如Spring管理的单例bean。扩展EntityManager实例只应该在有状态的组件中使用,例如驻留在会话中的组件,其生命周期不依赖于当前事务,而完全由应用程序控制。

方法和字段级别的注入

可以在类内的字段或方法上应用表示依赖注入的注解(如@PersistenceUnit@PersistenceContext)——因此有“方法级注入”和“字段级注入”的说法。字段级注解简洁且易于使用,而方法级注解允许对注入的依赖进行进一步处理。在这两种情况下,成员的可见性(公有、受保护或私有)并不重要。

类级别的注解呢?

在Jakarta EE平台上,它们用于依赖声明而不是资源注入。

注入的EntityManager由Spring管理(知道正在进行的事务)。尽管新的DAO实现使用EntityManager的方法级注入而不是EntityManagerFactory,但由于使用了注解,bean定义中不需要做任何更改。

这种DAO风格的主要优势是它仅依赖于Java持久化API。不需要导入任何Spring类。此外,由于理解了JPA注解,注入是由Spring容器自动应用的。从非侵入性的角度来看,这很有吸引力,对JPA开发人员来说可能感觉更自然。

基于@Autowired实现DAO(通常使用基于构造函数的注入)

@PersistenceUnit@PersistenceContext只能声明在方法和字段上。那么通过构造函数和其他@Autowired注入点提供JPA资源呢?

只要目标被定义为bean,例如通过LocalContainerEntityManagerFactoryBeanEntityManagerFactory可以轻松地通过构造函数和@Autowired字段/方法注入。注入点与原始的EntityManagerFactory定义按类型完全匹配。

然而,@PersistenceContext风格的共享EntityManager引用并不是默认可用的。为了使其能够根据@Autowired所需的类型匹配,考虑定义一个SharedEntityManagerBean作为EntityManagerFactory定义的伴随bean:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	...
</bean>

<bean id="em" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
	<property name="entityManagerFactory" ref="emf"/>
</bean>

或者,可以基于SharedEntityManagerCreator定义一个@Bean方法:

@Bean("em")
public static EntityManager sharedEntityManager(EntityManagerFactory emf) {
	return SharedEntityManagerCreator.createSharedEntityManager(emf);
}

在有多个持久化单元的情况下,每个EntityManagerFactory定义都需要有一个相应的EntityManager bean定义来伴随,理想情况下带有与不同的EntityManagerFactory定义匹配的限定符,以便通过@Autowired @Qualifier("…​")区分持久化单元。

Spring驱动的JPA事务

对于JPA,推荐的策略是通过JPA的本地事务支持进行本地事务。Spring的JpaTransactionManager提供了许多已知的本地JDBC事务功能(如事务特定的隔离级别和资源级别的只读优化),这些功能适用于任何常规的JDBC连接池,无需JTA事务协调器和支持XA的资源。

Spring JPA还允许配置的JpaTransactionManager将JPA事务暴露给访问相同DataSource的JDBC访问代码,只要注册的JpaDialect支持检索底层的JDBC Connection。Spring为EclipseLink和Hibernate JPA实现提供了方言。

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

理解JpaDialect 和 JpaVendorAdapter

作为一个高级特性,JpaTransactionManagerAbstractEntityManagerFactoryBean的子类允许将自定义的JpaDialect传递给jpaDialect bean属性。JpaDialect实现可以启用Spring支持的以下高级功能,通常以特定于供应商的方式:

  • 应用特定的事务语义(如自定义隔离级别或事务超时)
  • 检索事务性的JDBC Connection(以便暴露给基于JDBC的DAO)
  • PersistenceException高级转换为Spring的DataAccessException

这对于特殊的事务语义和异常的高级转换特别有价值。默认实现(DefaultJpaDialect)不提供任何特殊能力,如果需要前面列出的功能,必须指定适当的方言。

作为更广泛的提供者适配设施,主要用于Spring的全功能LocalContainerEntityManagerFactoryBean设置,JpaVendorAdapter结合了JpaDialect的功能和其他特定于提供者的默认设置。指定HibernateJpaVendorAdapterEclipseLinkJpaVendorAdapter是自动配置Hibernate或EclipseLink的EntityManagerFactory设置的最方便方式。请注意,这些提供者适配器主要设计用于与Spring驱动的事务管理(即与JpaTransactionManager一起使用)。

设置JPA与JTA事务管理

作为JpaTransactionManager的替代方案,Spring还允许通过JTA进行多资源事务协调,无论是在Jakarta EE环境中还是在独立的事务协调器(如Atomikos)中。除了选择Spring的JtaTransactionManager而不是JpaTransactionManager之外,还需要采取一些进一步的步骤:

  • 底层的JDBC连接池需要支持XA,并与事务协调器集成。在Jakarta EE环境中,这通常是直接的,通过JNDI暴露不同类型的DataSource。同样,独立的事务协调器通常也带有特殊的XA集成DataSource变体。
  • JPA EntityManagerFactory设置需要配置为JTA。这是特定于提供者的,通常通过在LocalContainerEntityManagerFactoryBean上指定jpaProperties的特殊属性来实现。在Hibernate的情况下,这些属性甚至是版本特定的。
  • Spring的HibernateJpaVendorAdapter强制执行某些面向Spring的默认设置,例如连接释放模式为on-close,这与Hibernate 5.0自己的默认设置相匹配,但在Hibernate 5.1+中不再是这样。对于JTA设置,请确保将持久化单元事务类型声明为“JTA”。或者,设置Hibernate 5.2的hibernate.connection.handling_mode属性为DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT以恢复Hibernate自己的默认设置。
  • 或者,考虑从应用程序服务器本身获取EntityManagerFactory(即通过JNDI查找而不是本地声明的LocalContainerEntityManagerFactoryBean)。服务器提供的EntityManagerFactory可能需要在服务器配置中进行特殊定义(使得部署不太可移植),但它是为服务器的JTA环境设置的。

原生Hibernate设置和与JPA交互的原生Hibernate事务

原生的LocalSessionFactoryBean设置与HibernateTransactionManager结合使用,允许与@PersistenceContext和其他JPA访问代码进行交互。现在,Hibernate SessionFactory原生实现了JPA的EntityManagerFactory接口,而Hibernate Session的处理原生地就是JPA EntityManager。Spring的JPA支持设施会自动检测原生Hibernate会话。

因此,在许多场景中,这样的原生Hibernate设置可以作为标准JPA LocalContainerEntityManagerFactoryBeanJpaTransactionManager组合的替代品,允许与SessionFactory.getCurrentSession()(以及HibernateTemplate)进行交互,而在同一个本地事务中也可以使用@PersistenceContext EntityManager。这样的设置还提供了更强的Hibernate集成和更多的配置灵活性,因为它不受JPA引导合同的限制。

在这种情况下,不需要HibernateJpaVendorAdapter配置,因为Spring的原生Hibernate设置提供了更多的功能(例如,自定义Hibernate Integrator设置、Hibernate 5.3 bean容器集成以及对只读事务的更强优化)。最后但同样重要的是,还可以通过LocalSessionFactoryBuilder表达原生Hibernate设置,与@Bean风格的配置无缝集成(不涉及FactoryBean)。

LocalSessionFactoryBeanLocalSessionFactoryBuilder支持后台引导,就像JPA的LocalContainerEntityManagerFactoryBean一样。

LocalSessionFactoryBean上,这可以通过bootstrapExecutor属性进行配置。对于程序化的LocalSessionFactoryBuilder,一个重载的buildSessionFactory方法接受一个引导执行器参数。

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值