跳到主要内容

JPA

ChatGPT-4o 中英对照 JPA

org.springframework.orm.jpa 包中的 Spring JPA 提供了对 Java Persistence 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>
xml

这种形式的 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>
xml

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

在这种情况下,整个持久化单元的部署,包括持久化类的编织(字节码转换),都由 Jakarta EE 服务器负责。JDBC DataSource 是通过 JNDI 位置在 META-INF/persistence.xml 文件中定义的。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>
xml

下面的示例展示了一个典型的 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>
xml
备注

<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 设置发生冲突。

什么时候需要加载时织入?

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

LoadTimeWeaver 接口是一个由 Spring 提供的类,它允许 JPA ClassTransformer 实例以特定的方式进行插入,这取决于环境是 Web 容器还是应用服务器。通过代理挂接 ClassTransformers 通常效率不高。代理会对整个虚拟机进行操作,并检查每一个被加载的类,这在生产服务器环境中通常是不受欢迎的。

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

请参阅 AOP 章节中的 Spring 配置,以获取有关 LoadTimeWeaver 实现及其设置的更多信息,这些设置可以是通用的,也可以是针对各种平台(如 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>
xml

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

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

无论 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>
<!-- 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>
xml

默认实现允许自定义 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>
xml

实际的 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,从而实际上使其使用线程安全。

可以在没有任何 Spring 依赖的情况下,使用注入的 EntityManagerFactoryEntityManager 编写纯 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();
}
}
}
}
java

前面的 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>
xml

作为显式定义 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>
xml

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

@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 Persistence API。不需要导入任何 Spring 类。此外,由于 JPA 注解是可以理解的,注入会由 Spring 容器自动应用。这在非侵入性方面具有吸引力,并且对于 JPA 开发人员来说可能感觉更自然。

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

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

只要目标被定义为一个 bean,例如通过 LocalContainerEntityManagerFactoryBeanEntityManagerFactory 就可以很容易地通过构造函数和 @Autowired 字段/方法注入。注入点按类型与原始的 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>
xml

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

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

在存在多个持久化单元的情况下,每个 EntityManagerFactory 定义都需要伴随一个相应的 EntityManager bean 定义,理想情况下应使用与不同的 EntityManagerFactory 定义相匹配的限定符,以便通过 @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 设置,JpaVendorAdapterJpaDialect 的功能与其他提供者特定的默认设置结合起来。指定一个 HibernateJpaVendorAdapterEclipseLinkJpaVendorAdapter 是为 Hibernate 或 EclipseLink 自动配置 EntityManagerFactory 设置的最便捷方式。请注意,这些提供者适配器主要是为与 Spring 驱动的事务管理一起使用而设计的(即,与 JpaTransactionManager 一起使用)。

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

使用 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 的情况下,这些属性甚至是特定于版本的。有关详细信息,请参阅您的 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 方法接受一个引导执行器参数。