JPA

Spring JPA (位于 org.springframework.orm.jpa 包下) 为 Java Persistence API 提供了全面的支持,其方式类似于与 Hibernate 的集成,同时也感知底层实现以提供附加功能。

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

Spring JPA 支持提供了三种设置 JPA EntityManagerFactory 的方式,应用程序使用 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 部署描述符(例如 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 设置发生冲突。

何时需要加载时织入(load-time weaving)?

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

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

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

关于 LoadTimeWeaver 实现及其设置(无论是通用的还是为各种平台(如 Tomcat、JBoss 和 WebSphere)定制的)的更多信息,请参见 AOP 章节中的 Spring 配置

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,使用此技术,依赖于 instrument 的 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>
	<!-- 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 时,请确保也为其仓库设置了延迟引导。

自 6.2 版本起,JPA 初始化在上下文刷新完成之前强制执行,在此之前等待异步引导完成。这使得完全初始化的数据库基础设施的可用性变得可预测,并允许在 ContextRefreshedEvent 监听器等地方编写自定义的初始化后逻辑。不建议将此类应用程序级别的数据库初始化放入 @PostConstruct 方法或类似位置;最好将其放在 Lifecycle.start(如果适用)或 ContextRefreshedEvent 监听器中。

基于 JPA 实现 DAO:EntityManagerFactoryEntityManager

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

通过使用注入的 EntityManagerFactoryEntityManager,可以编写纯粹基于 JPA 且不依赖于 Spring 的代码。如果启用了 PersistenceAnnotationBeanPostProcessor,Spring 可以理解字段和方法级别的 @PersistenceUnit@PersistenceContext 注解。以下示例展示了一个使用 @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>

	<!-- 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),而不是工厂。以下示例展示了如何这样做:

  • 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 实例仅应在有状态组件中使用,例如驻留在会话中,其 EntityManager 的生命周期不绑定到当前事务,而是完全由应用程序决定。

方法和字段级别的注入

您可以将表示依赖注入的注解(例如 @PersistenceUnit@PersistenceContext)应用于类中的字段或方法——因此有了“方法级别注入”和“字段级别注入”的说法。字段级别的注解简洁易用,而方法级别的注解允许对注入的依赖项进行进一步处理。在这两种情况下,成员的可访问性(public、protected 或 private)都不重要。

类级别注解呢?

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

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

这种 DAO 风格的主要优点是它只依赖于 Java Persistence API。无需导入任何 Spring 类。此外,由于 Spring 容器能够理解 JPA 注解,注入会自动应用。从非侵入性的角度来看,这很有吸引力,并且对 JPA 开发者来说可能感觉更自然。

基于 @Autowired 实现 DAO(通常结合构造函数注入)

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

只要目标被定义为一个 Bean(例如,通过 LocalContainerEntityManagerFactoryBean),就可以轻松地通过构造函数和 @Autowired 字段/方法注入 EntityManagerFactory。注入点按类型原样匹配原始的 EntityManagerFactory 定义。

然而,@PersistenceContext 风格的共享 EntityManager 引用并非开箱即用地可用于常规依赖注入。为了使其能够进行 @Autowired 所需的类型匹配,请考虑为您的 EntityManagerFactory 定义 companion 定义一个 SharedEntityManagerBean

<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 定义匹配的限定符(Qualifier),以便通过 @Autowired @Qualifier("…​") 来区分这些持久化单元。

Spring 驱动的 JPA 事务

我们强烈建议您阅读 声明式事务管理(如果您尚未阅读),以更详细地了解 Spring 的声明式事务支持。

对于 JPA 而言,推荐的策略是利用 JPA 的原生事务支持实现的本地事务。Spring 的 JpaTransactionManager 对任何常规 JDBC 连接池提供了许多本地 JDBC 事务已知的能力(例如事务特定的隔离级别和资源层只读优化),而无需 JTA 事务协调器和支持 XA 的资源。

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

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

理解 JpaDialectJpaVendorAdapter

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

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

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

  • PersistenceException 到 Spring 的 DataAccessException 的高级转换

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

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

有关 JpaDialectJpaVendorAdapter 的操作及其在 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 自己的默认设置匹配,但在 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 设置和原生 Hibernate 事务用于 JPA 交互

原生 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 方法接受一个引导执行器参数。