JPA
Spring JPA,位于org.springframework.orm.jpa包下,提供了对Java持久化API的全面支持,其集成方式与Hibernate类似,同时它也了解底层的实现细节,以便提供额外的功能。
在Spring环境中设置JPA的三种选择
Spring JPA支持三种设置JPA EntityManagerFactory的方法,应用程序使用该工厂来获取实体管理器(EntityManager)。
使用 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条目,并为这些持久化单元定义环境命名上下文的位置。
在这种情况下,整个持久化单元的部署,包括持久化类的字节码转换(weaving),都由Jakarta EE服务器负责。JDBC DataSource是通过META-INF/persistence.xml文件中的JNDI位置来定义的。EntityManager的事务与服务器的JTA子系统集成在一起。Spring只是使用获取到的EntityManagerFactory,通过依赖注入将其传递给应用程序对象,并管理持久化单元的事务(通常是通过JtaTransactionManager来实现的)。
如果在同一应用程序中使用多个持久化单元(persistence units),那么通过JNDI获取的这些持久化单元的bean名称应该与应用程序用来引用它们的持久化单元名称相匹配(例如,在@PersistenceUnit和@PersistenceContext注解中)。
使用 LocalContainerEntityManagerFactoryBean
您可以在基于Spring的应用环境中使用此选项来获得完整的JPA功能。这包括像Tomcat这样的Web容器、独立应用程序,以及具有复杂持久化要求的集成测试。
LocalContainerEntityManagerFactoryBean 完全控制着 EntityManagerFactory 的配置,非常适合需要细粒度定制的环境。LocalContainerEntityManagerFactoryBean 根据 persistence.xml 文件、提供的 dataSourceLookup 策略以及指定的 loadTimeWeaver 创建一个 PersistenceUnitInfo 实例。因此,可以处理 JNDI 之外的自定义数据源,并控制织入(weaving)过程。以下示例展示了 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连接,同时支持本地和全局事务等。然而,它也对运行时环境提出了一些要求,例如如果持久化提供者需要字节码转换,则必须具备具备编织(weaving)功能的类加载器。
此选项可能与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提供的一个类,它允许以特定的方式插入JPA的ClassTransformer实例,具体取决于环境是Web容器还是应用服务器。通过代理来连接ClassTransformers通常效率不高。这些代理会作用于整个虚拟机,并检查每一个被加载的类,而在生产服务器环境中,这样做通常是不可取的。
Spring为各种环境提供了多种LoadTimeWeaver实现,使得ClassTransformer实例可以仅针对每个类加载器(class loader)进行应用,而无需针对每个虚拟机(VM)都进行应用。
有关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 属性手动指定一个专用的织布工(weaver),如下例所示:
<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时,务必也要为其仓库设置延迟启动功能。
从JPA 6.2版本开始,强制在上下文刷新(context refresh)完成之前进行JPA初始化,等待异步启动(asynchronous bootstrapping)过程在此之前结束。这样可以让完全初始化的数据库基础设施的可用性变得可预测,并允许在ContextRefreshedEvent监听器等中添加自定义的初始化后逻辑。不建议将此类应用级别的数据库初始化代码放入@PostConstruct方法中;更好的做法是将其放在Lifecycle.start(如果适用)或ContextRefreshedEvent监听器中。
基于JPA实现DAO:EntityManagerFactory和EntityManager
虽然 EntityManagerFactory 实例是线程安全的,但 EntityManager 实例却不是。注入的 JPA EntityManager 的行为类似于从应用服务器的 JNDI 环境中获取的 EntityManager,这符合 JPA 规范的定义。如果存在当前事务性的 EntityManager,它会将所有调用委托给该 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”,这种EntityManager不是线程安全的,因此不能在需要同时访问的组件中使用,比如由Spring管理的单例Bean中。扩展型EntityManager实例仅应用于有状态的组件中,例如那些存在于会话(session)中的组件;这类组件的EntityManager生命周期并不与当前事务绑定,而是完全由应用程序来控制。
注入的EntityManager是由Spring管理的(能够感知到正在进行中的事务)。尽管新的DAO实现使用的是方法级别的EntityManager注入,而不是EntityManagerFactory,但由于使用了注解,因此在bean定义中不需要进行任何修改。
这种DAO风格的主要优点在于它仅依赖于Java Persistence API(JPA)。无需导入任何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的定义,理想情况下,这些EntityManager bean的定义应该带有与相应的EntityManagerFactory定义相匹配的限定符( qualifiers),以便通过@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实现提供了相应的方言(dialects)。有关JpaDialect的详细信息,请参阅下一节。
为了实现JTA风格的延迟获取实际资源连接,Spring为目标连接池提供了一个相应的DataSource代理类:参见LazyConnectionDataSourceProxy。这对于JPA只读事务来说尤其有用,因为这些事务通常可以从本地缓存中处理,而无需直接访问数据库。
理解 JpaDialect 和 JpaVendorAdapter
作为一个高级特性,JpaTransactionManager和AbstractEntityManagerFactoryBean的子类允许将自定义的JpaDialect传递到jpaDialect bean属性中。JpaDialect的实现可以启用Spring支持的以下高级特性,这些特性通常以特定于供应商的方式提供:
- 应用特定的事务语义(如自定义隔离级别或事务超时)
- 获取事务性的JDBC
Connection(以便与基于JDBC的DAOs交互) - 将
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自身的默认设置一致,但在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交互的Native Hibernate配置和Native 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集成器设置、与Hibernate 5.3 Bean容器的集成,以及对只读事务更强的优化)。最后但同样重要的是,你也可以通过LocalSessionFactoryBuilder来配置原生Hibernate,从而与@Bean风格的配置无缝集成(无需使用FactoryBean)。
LocalSessionFactoryBean 和 LocalSessionFactoryBuilder 支持后台引导(background bootstrapping),就像 JPA 的 LocalContainerEntityManagerFactoryBean 一样。有关介绍,请参阅 后台引导。
在 LocalSessionFactoryBean 中,可以通过 bootstrapExecutor 属性来实现这一功能。而在编程式的 LocalSessionFactoryBuilder 中,则有一个重载的 buildSessionFactory 方法,该方法会接受一个引导执行器(bootstrap executor)参数。