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 部署描述符(例如 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>
|
使用 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 容器还是应用程序服务器。通过
代理
挂钩 ClassTransformer
通常效率不高。代理对整个虚拟机起作用,
并检查加载的每个类,这在生产服务器环境中通常是不希望的。
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>
然而,如果需要,你可以通过 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>
<!-- 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
来编写不依赖于 Spring 的纯 JPA 代码。
如果启用了 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 类。此外,由于理解 JPA 注解,注入会自动由 Spring 容器应用。 从非侵入性的角度来看,这很有吸引力,并且对于 JPA 开发人员来说可能感觉更自然。
基于 @Autowired
实现 DAO(通常通过构造函数注入)
@PersistenceUnit
和 @PersistenceContext
只能在方法和字段上声明。
那么通过构造函数和其他 @Autowired
注入点提供 JPA 资源呢?
只要目标被定义为 bean,例如通过 LocalContainerEntityManagerFactoryBean
,EntityManagerFactory
就可以通过构造函数和 @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>
或者,你可以定义一个基于 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
支持检索底层 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 功能齐全的
|
请参阅 JpaDialect
和
JpaVendorAdapter
的 Javadoc,
以获取其操作和它们在 Spring JPA 支持中如何使用的更多详细信息。
使用 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 环境进行设置。
用于 JPA 交互的原生 Hibernate 设置和原生 Hibernate 事务
原生 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
)。
|