JPA

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

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

Spring JPA支持提供了三种设置JPA EntityManagerFactory的方式,应用程序可以使用这些方式来获取实体管理器。

使用 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部署描述符中的persistence-unit-ref条目(例如web.xml),并为这些持久性单元定义环境命名上下文位置。

在这种情况下,整个持久性单元部署,包括持久类的编织(字节码转换),由Jakarta EE服务器负责。JDBC DataSourceMETA-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容器还是应用服务器。通过代理挂钩ClassTransformers通常不高效。代理针对整个虚拟机工作并检查加载的每个类,这在生产服务器环境中通常是不希望的。

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

有关LoadTimeWeaver实现及其设置的更多见解,请参阅AOP章节中的Spring配置。这些配置可以是通用的,也可以根据不同平台(如Tomcat、JBoss和WebSphere)进行定制。

如在Spring配置中所述,您可以通过使用@EnableLoadTimeWeaving注解或context:load-time-weaver XML元素来配置一个全局的LoadTimeWeaver。这样的全局编织器会自动被所有JPA LocalContainerEntityManagerFactoryBean实例所使用。以下示例显示了设置加载时编织器的首选方法,提供平台的自动检测(例如Tomcat的编织能力类加载器或Spring的JVM代理)并自动将编织器传播到所有了解编织器的bean:

<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转换器仅在类加载器级别应用,因此彼此之间是隔离的。

处理多个持久性单元

对于依赖于多个持久性单元位置(例如存储在类路径中的各种JAR文件中)的应用程序,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>
	<!-- 如果未指定数据源,则使用此数据源 -->
	<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)的定制。如果未指定PersistenceUnitManager,则会创建一个并在LocalContainerEntityManagerFactoryBean内部使用。

背景引导

LocalContainerEntityManagerFactoryBean通过bootstrapExecutor属性支持背景引导,如下例所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="bootstrapExecutor">
		<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
	</property>
</bean>
EntityManagerFactory代理可以被注入到其他应用程序组件中,甚至能够响应 EntityManagerFactoryInfo配置检查。然而,一旦实际的JPA提供程序被其他组件访问(例如,调用 createEntityManager),这些调用会阻塞,直到背景引导完成。特别是当您使用Spring Data JPA时,请确保为其存储库设置延迟引导。

基于JPA实现DAO: EntityManagerFactoryEntityManager

虽然 EntityManagerFactory 实例是线程安全的,但 EntityManager 实例不是。注入的JPA EntityManager 的行为类似于从应用服务器的JNDI环境中获取的 EntityManager,如JPA规范所定义。如果存在当前事务的话,它会将所有调用委托给当前事务的 EntityManager。否则,它会回退到每个操作创建一个新的 EntityManager,从而使其在使用时线程安全。

可以在没有任何Spring依赖的情况下针对纯JPA编写代码,通过使用注入的 EntityManagerFactoryEntityManager。Spring可以理解在字段和方法级别同时使用 @PersistenceUnit@PersistenceContext 注解,如果启用了一个 PersistenceAnnotationBeanPostProcessor。以下示例展示了一个使用 @PersistenceUnit 注解的纯JPA DAO实现:

  • Java

  • Kotlin

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();
			}
		}
	}
}
class ProductDaoImpl : ProductDao {

	private lateinit var emf: EntityManagerFactory

	@PersistenceUnit
	fun setEntityManagerFactory(emf: EntityManagerFactory) {
		this.emf = emf
	}

	fun loadProductsByCategory(category: String): Collection<*> {
		val em = this.emf.createEntityManager()
		val query = em.createQuery("from Product as p where p.category = ?1");
		query.setParameter(1, category);
		return query.resultList;
	}
}

上述DAO不依赖于Spring,并且可以很好地适配Spring应用程序上下文。此外,DAO利用注解来要求注入默认的 EntityManagerFactory,如下面的示例bean定义所示:

<beans>

	<!-- 用于JPA注解的bean后处理器 -->
	<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

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

</beans>

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

考虑以下示例:

<beans>

	<!-- 所有标准配置注解的后处理器 -->
	<context:annotation-config/>

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

</beans>

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

  • Java

  • Kotlin

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();
	}
}
class ProductDaoImpl : ProductDao {

	@PersistenceContext
	private lateinit var em: EntityManager

	fun loadProductsByCategory(category: String): Collection<*> {
		val query = em.createQuery("from Product as p where p.category = :category")
		query.setParameter("category", category)
		return query.resultList
	}
}

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

方法和字段级别的注入

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

那么类级别的注解呢?

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

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

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

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

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

EntityManagerFactory 可以很容易地通过构造函数和 @Autowired 字段/方法进行注入,只要目标被定义为一个bean,例如通过 LocalContainerEntityManagerFactoryBean。注入点与原始 EntityManagerFactory 定义按类型匹配。

然而,类似于 @PersistenceContext 风格的共享 EntityManager 引用不能直接用于常规依赖注入。为了使其可用于按类型匹配,如 @Autowired 所需的那样,考虑定义一个 SharedEntityManagerBean 作为您的 EntityManagerFactory 定义的伴侣:

<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事务

如果您尚未这样做,我们强烈建议您阅读声明式事务管理,以获取有关Spring声明式事务支持的更详细覆盖范围。

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

Spring JPA还允许配置的JpaTransactionManager将JPA事务暴露给访问相同DataSource的JDBC访问代码,前提是注册的JpaDialect支持检索底层的JDBCConnection。Spring为EclipseLink和Hibernate JPA实现提供了方言。有关JpaDialect的详细信息,请参见下一节。

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

理解JpaDialectJpaVendorAdapter

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

  • 应用特定的事务语义(例如自定义隔离级别或事务超时)

  • 检索事务性JDBCConnection(用于向基于JDBC的DAO暴露)

  • PersistenceException的高级转换为Spring的DataAccessException

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

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

有关其操作及其在Spring的JPA支持中的使用方式的更多详细信息,请参阅JpaDialectJpaVendorAdapter的javadoc。

使用JTA事务管理设置JPA

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

  • 底层的JDBC连接池需要支持XA,并与您的事务协调器集成。在Jakarta EE环境中,这通常很简单,通过JNDI公开不同类型的DataSource。有关详细信息,请参阅您的应用服务器文档。类似地,独立事务协调器通常配备了特殊的XA集成DataSource变体。再次,请查看其文档。

  • JPA的EntityManagerFactory设置需要配置为JTA。这是特定于提供商的,通常通过在LocalContainerEntityManagerFactoryBean上指定为jpaProperties的特殊属性来实现。在Hibernate的情况下,这些属性甚至是特定于版本的。有关详细信息,请参阅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自己的默认值。有关相关说明,请参阅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设置可以在许多情况下替代标准的JPALocalContainerEntityManagerFactoryBeanJpaTransactionManager组合,允许在同一本地事务中与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方法接受一个引导执行器参数。