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部署描述符中的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提供的一个类,它允许JPA ClassTransformer
实例以特定方式插入,具体取决于环境是Web容器还是应用服务器。通过代理挂钩ClassTransformers
通常不高效。代理针对整个虚拟机工作并检查加载的每个类,这在生产服务器环境中通常是不希望的。
Spring为各种环境提供了多个LoadTimeWeaver
实现,让ClassTransformer
实例仅适用于每个类加载器,而不是每个虚拟机。
有关LoadTimeWeaver
实现及其设置的更多见解,请参阅AOP章节中的Spring配置。这些配置可以是通用的,也可以根据不同平台(如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>
<!-- 如果未指定数据源,则使用此数据源 -->
<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>
EntityManagerFactory
代理可以被注入到其他应用程序组件中,甚至能够响应
EntityManagerFactoryInfo
配置检查。然而,一旦实际的JPA提供程序被其他组件访问(例如,调用
createEntityManager
),这些调用会阻塞,直到背景引导完成。特别是当您使用Spring Data JPA时,请确保为其存储库设置延迟引导。
基于JPA实现DAO: EntityManagerFactory
和 EntityManager
虽然 EntityManagerFactory 实例是线程安全的,但 EntityManager 实例不是。注入的JPA EntityManager 的行为类似于从应用服务器的JNDI环境中获取的 EntityManager ,如JPA规范所定义。如果存在当前事务的话,它会将所有调用委托给当前事务的 EntityManager 。否则,它会回退到每个操作创建一个新的 EntityManager ,从而使其在使用时线程安全。 |
可以在没有任何Spring依赖的情况下针对纯JPA编写代码,通过使用注入的 EntityManagerFactory
或 EntityManager
。Spring可以理解在字段和方法级别同时使用 @PersistenceUnit
和 @PersistenceContext
注解,如果启用了一个 PersistenceAnnotationBeanPostProcessor
。以下示例展示了一个使用 @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>
<!-- 用于JPA注解的bean后处理器 -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
作为显式定义 PersistenceAnnotationBeanPostProcessor
的替代方案,考虑在应用程序上下文配置中使用Spring的 context:annotation-config
XML元素。这样做会自动注册所有基于注解的配置的Spring标准后处理器,包括 CommonAnnotationBeanPostProcessor
等。
考虑以下示例:
<beans>
<!-- 所有标准配置注解的后处理器 -->
<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
是由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
所需的那样,考虑定义一个 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
支持检索底层的JDBCConnection
。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 一起使用)。 |
有关其操作及其在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 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设置可以在许多情况下替代标准的JPALocalContainerEntityManagerFactoryBean
和JpaTransactionManager
组合,允许在同一本地事务中与SessionFactory.getCurrentSession()
(以及HibernateTemplate
)以及@PersistenceContext EntityManager
进行交互。这种设置还提供了更强大的Hibernate集成和更多的配置灵活性,因为它不受JPA引导合同的约束。
在这种情况下,您不需要进行HibernateJpaVendorAdapter
配置,因为Spring的本地Hibernate设置提供了更多功能(例如,自定义Hibernate Integrator设置,Hibernate 5.3 bean容器集成以及更强大的只读事务优化)。最后,您还可以通过LocalSessionFactoryBuilder
表达本地Hibernate设置,与@Bean
样式配置无缝集成(不涉及FactoryBean
)。
在 |