|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
JPA
Spring JPA,可在 org.springframework.orm.jpa 包下获得,提供了对
Java Persistence
API 的全面支持,其方式类似于与 Hibernate 的集成,同时了解底层实现以提供额外的功能。
Spring环境中JPA配置的三种方案
Spring JPA 支持提供了三种设置应用程序用于获取实体管理器的 JPA EntityManagerFactory 的方法。
使用 LocalEntityManagerFactoryBean
你只能在简单的部署环境(如独立应用程序和集成测试)中使用此选项。
LocalEntityManagerFactoryBean 会创建一个适合于仅使用 JPA 进行数据访问的简单部署环境的 EntityManagerFactory。
工厂 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
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 设置发生冲突(例如,这适用于 Resin 3.1)。
LoadTimeWeaver 接口是 Spring 提供的类,它允许根据环境是 Web 容器还是应用服务器,以特定方式插入 JPA
ClassTransformer 实例。通过
代理
连接 ClassTransformers
通常效率不高。代理会针对整个虚拟机工作,并检查所有加载的类,这在生产服务器环境中通常是不希望的。
Spring 为各种环境提供了多个 LoadTimeWeaver 实现,
允许每个类加载器仅应用 ClassTransformer 个实例,而不是每个虚拟机。
查看AOP章节中的Spring配置,以获得更多关于LoadTimeWeaver实现及其设置的见解,无论是通用的还是针对各种平台(如Tomcat、JBoss和WebSphere)的定制版本。
如LoadTimeWeaver中所述,可以通过使用@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属性手动指定一个专用的weaver,如下例所示:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</property>
</bean>
无论LTW如何配置,通过此技术,依赖于instrumentation的JPA应用程序可以在目标平台(例如Tomcat)上运行,而无需使用代理。当托管的应用程序依赖于不同的JPA实现时,这一点尤为重要,因为JPA转换器仅在类加载器级别应用,因此彼此之间是隔离的。
处理多个持久化单元
对于依赖多个持久化单元位置(例如存储在类路径中的不同JAR中)的应用程序,Spring提供了PersistenceUnitManager作为中心仓库,以避免耗时的持久化单元发现过程。默认实现允许指定多个位置。这些位置会被解析,并通过持久化单元名称 later 检索。(默认情况下,会搜索类路径中的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提供程序引导由指定的执行器处理,然后,以并行方式,由应用程序引导线程处理。公开的<code>0</code> 代理可以注入到其他应用程序组件中,并且甚至能够响应<code>1</code>配置检查。但是,一旦实际的JPA提供程序被其他组件访问(例如,调用<code>2</code>),这些调用将阻塞,直到后台引导完成。特别是,当您使用Spring Data JPA时,请确保为其仓库也设置延迟引导。
基于JPA实现DAO:EntityManagerFactory和EntityManager
虽然 EntityManagerFactory 实例是线程安全的,但 EntityManager 实例不是。注入的 JPA EntityManager 的行为类似于从应用服务器的 JNDI 环境中获取的 EntityManager,如 JPA 规范所定义。它会将所有调用委托给当前事务的 EntityManager,如果有的话。否则,它会回退到每个操作中创建一个新的 EntityManager,从而使其使用线程安全。 |
可以在不使用任何Spring依赖项的情况下针对普通JPA编写代码,通过使用注入的EntityManagerFactory或EntityManager。如果启用了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持久化API。 不需要导入任何Spring类。此外,由于JPA注解被理解, Spring容器会自动应用注入。从非侵入性的角度来看,这很有吸引力,并且对JPA开发者来说可能感觉更自然。
基于@Autowired实现DAO(通常使用基于构造函数的注入)
@PersistenceUnit 和 @PersistenceContext 只能在方法和字段上声明。
那么如何通过构造函数和其他 @Autowired 注入点来提供 JPA 资源呢?
EntityManagerFactory可以通过构造函数和@Autowired字段/方法轻松注入,只要目标被定义为bean(例如通过LocalContainerEntityManagerFactoryBean)。注入点会按原始EntityManagerFactory定义的类型进行匹配。
然而,一个@PersistenceContext样式的共享EntityManager引用无法直接通过常规依赖注入开箱即用。为了使其支持@Autowired所要求的基于类型匹配,请考虑为您的EntityManagerFactory定义配置一个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定义都需要一个对应的EntityManagerbean定义,理想情况下,这些限定符应与不同的EntityManagerFactory定义相匹配,以便通过@Autowired @Qualifier("…")区分持久化单元。
Spring驱动的JPA事务
| 我们强烈建议您阅读 声明式事务管理,如果您尚未这样做的话,以获得有关 Spring 声明式事务支持的更详细内容。 |
对于JPA的推荐策略是通过JPA的原生事务支持进行本地事务。Spring的JpaTransactionManager在任何常规JDBC连接池上提供了许多从本地JDBC事务中熟知的功能(例如事务特定的隔离级别和资源级别的只读优化)(无需XA要求)。
Spring JPA 还允许配置的 JpaTransactionManager 将 JPA 事务暴露给访问相同 DataSource 的 JDBC 访问代码,前提是注册的 JpaDialect 支持检索底层 JDBC Connection。
Spring 为 EclipseLink 和 Hibernate JPA 实现提供了方言。
有关 JpaDialect 机制的详细信息,请参阅下一节。
理解 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 的 javadoc 以了解其操作的更多详细信息以及它们在 Spring 的 JPA 支持中的使用方法。
使用JTA事务管理设置JPA
作为JpaTransactionManager的替代方案,Spring 还支持通过 JTA 实现多资源事务协调,无论是在雅加达 EE 环境中还是使用独立事务协调器(如 Atomikos)。除了选择 Spring 的 JtaTransactionManager 而非 JpaTransactionManager 之外,您只需执行以下几个额外步骤:
-
底层JDBC连接池需要支持XA,并与您的事务协调器集成。在Jakarta EE环境中,这通常很直接,通过JNDI暴露一种不同的
DataSource。有关详细信息,请参阅您的应用程序服务器文档。类似地,独立的事务协调器通常提供特殊的XA集成DataSource变体。再次,请检查其文档。 -
JPA
EntityManagerFactory设置需要为 JTA 进行配置。这取决于提供者,通常通过作为jpaProperties指定的特殊属性来设置在LocalContainerEntityManagerFactoryBean上。在 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配置可以在许多情况下作为标准JPA
LocalContainerEntityManagerFactoryBean和JpaTransactionManager组合的替代方案,
允许在同一个本地事务中与SessionFactory.getCurrentSession()
(以及HibernateTemplate)一起使用@PersistenceContext EntityManager。这种配置还提供了更强的Hibernate集成
和更多的配置灵活性,因为它不受JPA启动契约的限制。
在这种情况下,您不需要HibernateJpaVendorAdapter配置,
因为Spring原生的Hibernate设置提供了更多功能
(例如,自定义Hibernate Integrator配置,Hibernate 5.3 bean容器集成,
以及对只读事务的更强优化)。最后但同样重要的是,您也可以通过LocalSessionFactoryBuilder表达原生的Hibernate配置,
与@Bean样式配置无缝集成(不涉及FactoryBean)。
|
在 |