JPA

Spring JPA(位于org.springframework.orm.jpa包下)以类似于Hibernate集成的方式提供了对Java持久化API的全面支持,同时了解底层实现以提供附加功能。

在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(例如在Jakarta EE环境中)获取EntityManagerFactory,只需更改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 设置发生冲突。

何时需要加载时编织?

并非所有 JPA 提供程序都需要 JVM 代理。Hibernate就是一个不需要的例子。如果您的提供程序不需要代理,或者您有其他替代方案,例如通过自定义编译器或 Ant 任务在构建时应用增强,则不应使用加载时编织器。

LoadTimeWeaver接口是 Spring 提供的一个类,它允许以特定方式插入 JPA ClassTransformer 实例,具体取决于环境是 Web 容器还是应用程序服务器。通过代理连接ClassTransformers通常效率不高。代理针对整个虚拟机工作,并检查加载的每个类,这在生产服务器环境中通常是不希望的。

Spring 为各种环境提供许多LoadTimeWeaver实现,允许仅为每个类加载器应用ClassTransformer实例,而不是为每个虚拟机应用。

请参阅 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:EntityManagerFactoryEntityManager

虽然EntityManagerFactory实例是线程安全的,但EntityManager实例不是。注入的 JPA EntityManager的行为类似于从应用程序服务器的 JNDI 环境中获取的EntityManager,如 JPA 规范中所定义。它将所有调用委托给当前的事务EntityManager(如果存在)。否则,它将为每个操作回退到新创建的EntityManager,从而使其使用线程安全。

通过使用注入的EntityManagerFactoryEntityManager,可以编写针对普通 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的生命周期不与当前事务绑定,而是完全取决于应用程序。

方法级和字段级注入

您可以在类中的字段或方法上应用指示依赖项注入的注解(例如@PersistenceUnit@PersistenceContext)——因此有了“方法级注入”和“字段级注入”这两个说法。字段级注解简洁易用,而方法级注解允许对注入的依赖项进行进一步处理。在这两种情况下,成员可见性(public、protected 或 private)都没有关系。

类级注解怎么样?

在 Jakarta EE 平台上,它们用于依赖声明,而不是资源注入。

注入的EntityManager由 Spring 管理(知道正在进行的事务)。即使新的 DAO 实现使用EntityManager的方法级注入而不是EntityManagerFactory,由于使用了注解,bean 定义也不需要更改。

这种 DAO 样式的主要优点是它只依赖于 Java 持久性 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支持检索底层的JDBCConnection。Spring为EclipseLink和Hibernate JPA实现提供了方言。有关JpaDialect的详细信息,请参阅下一节

对于实际资源连接的JTA风格延迟检索,Spring为目标连接池提供了一个相应的DataSource代理类:参见LazyConnectionDataSourceProxy。这对于JPA只读事务特别有用,因为这些事务通常可以从本地缓存中处理,而不是访问数据库。

理解JpaDialectJpaVendorAdapter

作为一个高级功能,JpaTransactionManagerAbstractEntityManagerFactoryBean的子类允许将自定义JpaDialect传递到jpaDialect bean属性中。JpaDialect实现可以启用Spring支持的以下高级功能,通常以厂商特定的方式

  • 应用特定的事务语义(例如自定义隔离级别或事务超时)

  • 检索事务性JDBCConnection(用于暴露给基于JDBC的DAO)

  • 高级PersistenceException到Spring的DataAccessException的转换

这对于特殊的事务语义和异常的高级转换特别有价值。默认实现(DefaultJpaDialect)不提供任何特殊功能,如果需要前面列出的功能,则必须指定相应的方言。

作为更广泛的提供程序适配功能,主要用于Spring的完整功能LocalContainerEntityManagerFactoryBean设置,JpaVendorAdapter结合了JpaDialect和其他提供程序特定的默认值的功能。指定HibernateJpaVendorAdapterEclipseLinkJpaVendorAdapter是分别自动配置Hibernate或EclipseLink的EntityManagerFactory设置的最方便方法。请注意,这些提供程序适配器主要设计用于与Spring驱动的交易管理一起使用(即与JpaTransactionManager一起使用)。

有关其操作以及如何在Spring的JPA支持中使用它们的更多详细信息,请参阅JpaDialectJpaVendorAdapter 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交互的原生Hibernate设置和原生Hibernate事务

结合HibernateTransactionManager使用原生的LocalSessionFactoryBean设置允许与@PersistenceContext和其他JPA访问代码进行交互。Hibernate SessionFactory现在原生实现JPA的EntityManagerFactory接口,而Hibernate Session句柄原生就是一个JPA EntityManager。Spring的JPA支持工具会自动检测原生的Hibernate会话。

因此,这种原生的Hibernate设置可以在许多情况下替代标准的JPA LocalContainerEntityManagerFactoryBeanJpaTransactionManager组合,允许在同一本地事务中与SessionFactory.getCurrentSession()(以及HibernateTemplate)和@PersistenceContext EntityManager进行交互。这种设置还提供了更强大的Hibernate集成和更多配置灵活性,因为它不受JPA引导契约的约束。

在这种情况下,您不需要HibernateJpaVendorAdapter配置,因为Spring的原生Hibernate设置提供了更多功能(例如,自定义Hibernate集成程序设置、Hibernate 5.3 bean容器集成以及对只读事务的更强大的优化)。最后但并非最不重要的一点是,您还可以通过LocalSessionFactoryBuilder表达原生的Hibernate设置,与@Bean样式配置无缝集成(不涉及FactoryBean)。

LocalSessionFactoryBeanLocalSessionFactoryBuilder支持后台引导,就像JPA LocalContainerEntityManagerFactoryBean一样。有关介绍,请参阅后台引导

LocalSessionFactoryBean上,这可以通过bootstrapExecutor属性实现。在程序化的LocalSessionFactoryBuilder上,重载的buildSessionFactory方法采用引导执行程序参数。