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 设置发生冲突。
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
)进行。如果未指定 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>
实际的 JPA 提供商引导过程被交给了指定的执行器,然后并行于应用程序引导线程运行。暴露的 EntityManagerFactory
代理可以注入到其他应用程序组件中,甚至能够响应 EntityManagerFactoryInfo
配置检查。但是,一旦实际的 JPA 提供商被其他组件访问(例如,调用 createEntityManager
),这些调用将阻塞,直到后台引导完成。特别是当您使用 Spring Data JPA 时,请确保也为其仓库设置了延迟引导。
自 6.2 版本起,JPA 初始化在上下文刷新完成之前强制执行,在此之前等待异步引导完成。这使得完全初始化的数据库基础设施的可用性变得可预测,并允许在 ContextRefreshedEvent
监听器等地方编写自定义的初始化后逻辑。不建议将此类应用程序级别的数据库初始化放入 @PostConstruct
方法或类似位置;最好将其放在 Lifecycle.start
(如果适用)或 ContextRefreshedEvent
监听器中。
基于 JPA 实现 DAO:EntityManagerFactory
和 EntityManager
尽管 EntityManagerFactory 实例是线程安全的,但 EntityManager 实例不是。注入的 JPA EntityManager 的行为类似于从应用服务器的 JNDI 环境中获取的 EntityManager ,如 JPA 规范所定义。它将所有调用委托给当前的事务性 EntityManager (如果存在)。否则,它会回退到为每个操作新创建的 EntityManager ,实际上使其使用是线程安全的。 |
通过使用注入的 EntityManagerFactory
或 EntityManager
,可以编写纯粹基于 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
的生命周期不绑定到当前事务,而是完全由应用程序决定。
注入的 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 只读事务尤其有用,因为只读事务通常可以从本地缓存处理,而无需访问数据库。
理解 JpaDialect
和 JpaVendorAdapter
作为一项高级特性,JpaTransactionManager
和 AbstractEntityManagerFactoryBean
的子类允许将自定义的 JpaDialect
传递到 jpaDialect
bean 属性中。JpaDialect
实现可以启用 Spring 支持的以下高级特性,通常是以特定于供应商的方式
-
应用特定的事务语义(例如自定义隔离级别或事务超时)
-
检索事务性 JDBC
Connection
(用于暴露给基于 JDBC 的 DAO) -
PersistenceException
到 Spring 的DataAccessException
的高级转换
这对于特殊的事务语义和高级异常转换特别有价值。默认实现(DefaultJpaDialect
)不提供任何特殊能力,如果需要前面列出的特性,则必须指定适当的方言。
作为一项更广泛的提供商适应设施,主要用于 Spring 全功能 LocalContainerEntityManagerFactoryBean 设置,JpaVendorAdapter 结合了 JpaDialect 的能力与其他提供商特定的默认设置。指定 HibernateJpaVendorAdapter 或 EclipseLinkJpaVendorAdapter 分别是为 Hibernate 或 EclipseLink 自动配置 EntityManagerFactory 设置最方便的方式。请注意,这些提供商适配器主要设计用于 Spring 驱动的事务管理(即,与 JpaTransactionManager 一起使用)。 |
有关 JpaDialect
和 JpaVendorAdapter
的操作及其在 Spring JPA 支持中的使用方式的更多详细信息,请参阅其 JpaDialect
和 JpaVendorAdapter
的 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 LocalContainerEntityManagerFactoryBean
和 JpaTransactionManager
组合的替代方案,允许在同一本地事务中与 SessionFactory.getCurrentSession()
(以及 HibernateTemplate
)以及 @PersistenceContext EntityManager
进行交互。这种设置还提供了更强大的 Hibernate 集成和更高的配置灵活性,因为它不受 JPA 引导契约的限制。
在这种情况下,您不需要 HibernateJpaVendorAdapter
配置,因为 Spring 的原生 Hibernate 设置提供了更多特性(例如,自定义 Hibernate Integrator 设置,Hibernate 5.3 bean 容器集成,以及更强大的只读事务优化)。最后但同样重要的是,您还可以通过 LocalSessionFactoryBuilder
表达原生 Hibernate 设置,与 @Bean
风格的配置无缝集成(不涉及 FactoryBean
)。
在 |