4.4JPA

可以在org.springframework.orm.jpa包下获得的Spring JPA以类似于与Hibernate集成的方式,为Java Persistence API提供了全面的支持,同时使用到了底层实现以提供附加功能。

4.4.1 在Spring环境中设置JPA的三个选项

Spring JPA支持提供了三种设置JPA EntityManagerFactory的方法,供应用程序用来获取实体管理器。

  • 使用LocalEntityManagerFactoryBean

  • 从JNDI获取EntityManagerFactory

  • 使用LocalContainerEntityManagerFactoryBean

使用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

部署到Java EE服务器时,可以使用此选项。 查看服务器的文档,以了解如何将自定义JPA提供程序部署到服务器中,以允许使用与服务器默认设置不同的提供程序。

从JNDI获得EntityManagerFactory(例如在Java EE环境中)是更改XML配置的问题,如以下示例所示:

<beans>
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>

此操作假定标准Java EE引导。 Java EE服务器自动检测Java EE部署描述符中的持久性单元(实际上是应用程序jar中的META-INF / persistence.xml文件)和persistence-unit-ref条目,并定义环境命名这些持久性单元的上下文位置。

在这种情况下,整个持久性单元部署,包括持久性类的编织(字节码转换),都取决于Java EE服务器。 JDBC数据源是通过META-INF / persistence.xml文件中的JNDI位置定义的。 EntityManager事务与服务器的JTA子系统集成在一起。 Spring仅使用获得的EntityManagerFactory,通过依赖关系注入将其传递给应用程序对象,并管理持久性单元的事务(通常通过JtaTransactionManager)。

如果在同一应用程序中使用多个持久性单元,则此类JNDI检索的持久性单元的bean名称应与应用程序用来引用它们的持久性单元名称匹配(例如,在@PersistenceUnit和@PersistenceContext批注中)。

使用LocalContainerEntityManagerFactoryBean 您可以在基于Spring的应用程序环境中将此选项用于完整的JPA功能。这包括Web容器(例如Tomcat),独立应用程序以及具有复杂持久性要求的集成测试。

如果要专门配置Hibernate设置,则直接的替代方法是使用Hibernate 5.2或5.3并设置本机Hibernate LocalSessionFactoryBean而不是纯JPA LocalContainerEntityManagerFactoryBean,从而使其与JPA访问代码以及本机Hibernate访问代码交互。 有关详细信息,请参见用于JPA交互的本机Hibernate设置。

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-unlist-classes> true </ exclude-unlist-classes />)也意味着不进行扫描。 <exclude-unlisted-classes> false </ exclude-unlisted-classes />会触发扫描。但是,如果您希望进行实体类扫描,我们建议省略exclude-unlisted-classes元素。

使用LocalContainerEntityManagerFactoryBean是最强大的JPA设置选项,可以在应用程序中进行灵活的本地配置。它支持到现有JDBC数据源的链接,同时支持本地和全局事务,等等。但是,这也对运行时环境提出了要求,例如,如果持久性提供程序要求字节码转换,则需要具有可用的可编织类加载器。

此选项可能与Java EE服务器的内置JPA功能冲突。在完整的Java EE环境中,请考虑从JNDI获取您的EntityManagerFactory。或者,在LocalContainerEntityManagerFactoryBean定义上指定自定义persistenceXmlLocation(例如,META-INF / my-persistence.xml),并在应用程序jar文件中仅包含具有该名称的描述符。由于Java EE服务器仅查找默认的META-INF / persistence.xml文件,因此它会忽略此类自定义持久性单元,因此避免了与Spring预先驱动的JPA设置发生冲突。 (例如,这适用于Resin 3.1。)

什么时候需要加载时编织?

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

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

Spring为各种环境提供了许多LoadTimeWeaver实现,让ClassTransformer实例仅应用于每个类加载器,而不应用于每个VM。

请参阅AOP章节中的Spring配置以获取有关LoadTimeWeaver实现及其设置的更多信息,这些实现是通用的或针对各种平台(例如Tomcat,JBoss和WebSphere)定制的。

如Spring配置中所述,您可以通过使用context:load-time-weaver XML元素的@EnableLoadTimeWeaving批注来配置上下文范围的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转换器仅在类加载器级别应用,因此彼此隔离。

处理多个持久性单元

对于依赖多个持久性单元位置的应用程序(例如,存储在类路径中的各种JARS中),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>

默认实现允许以声明方式(通过影响所有托管单元的属性)或以编程方式(通过允许选择持久单元的PersistenceUnitPostProcessor)自定义PersistenceUnitInfo实例(在将其馈送到JPA提供程序之前)。 如果未指定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时,请确保还为其存储库设置了延迟引导。

4.4.2 基于JPA的DAO实现:EntityManagerFactory和EntityManager

尽管EntityManagerFactory实例是线程安全的,但EntityManager实例不是。 注入的JPA EntityManager的行为就像是从应用程序服务器的JNDI环境中提取的EntityManager一样,如JPA规范所定义。 它将所有调用委派给当前的事务性EntityManager(如果有)。 否则,它会退回到每个操作新创建的EntityManager上,实际上使它的使用成为线程安全的。

通过使用注入的EntityManagerFactory或EntityManager,可以在没有任何Spring依赖项的情况下针对普通JPA编写代码。 如果启用了PersistenceAnnotationBeanPostProcessor,Spring可以在字段和方法级别上了解@PersistenceUnit和@PersistenceContext批注。 以下示例显示了使用@PersistenceUnit批注的普通JPA DAO实现:

public class ProductDaoImpl implements ProductDao {
private EntityManagerFactory emf;
@PersistenceUnit
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
public Collection loadProductsByCategory(String category) {
try (EntityManager em = this.emf.createEntityManager()) {
Query query = em.createQuery("from Product as p where p.category = ?1");
query.setParameter(1, category);
return query.getResultList();
}
}
}

前面的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的共享的线程安全代理)来代替工厂注入,从而避免了这种情况。 以下示例显示了如何执行此操作:

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();
}
}

@PersistenceContext批注具有一个称为type的可选属性,默认为PersistenceContextType.TRANSACTION。 您可以使用此默认值来接收共享的EntityManager代理。 替代方案PersistenceContextType.EXTENDED是完全不同的事情。 这导致了所谓的扩展EntityManager,它不是线程安全的,因此不能在并发访问的组件(例如Spring管理的单例bean)中使用。 扩展的EntityManager实例仅应用于有状态的组件(例如,驻留在会话中),并且EntityManager的生命周期不依赖于当前事务,而完全取决于应用程序。

方法级和字段级注入

您可以在类中的字段或方法上应用指示依赖项注入的注解(例如@PersistenceUnit和@PersistenceContext),因此表达式为“方法级注入”和“字段级注入”。字段级注解简洁明了,易于使用,而方法级注解则允许对注入的依赖项进行进一步处理。在这两种情况下,成员的可见性(公共,受保护或私有)都无关紧要。

那类级别的注解呢?

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

注入的EntityManager是Spring管理的(意识到正在进行的事务)。即使新的DAO实现使用EntityManager而不是EntityManagerFactory的方法级注入,由于注解的使用,应用程序上下文XML也不需要更改。

这种DAO样式的主要优点是它仅取决于Java Persistence API。不需要导入任何Spring类。此外,由于可以理解JPA批注,因此Spring容器会自动应用注入。从非侵入性的角度来看,这是有吸引力的,并且对于JPA开发人员而言,感觉会更自然。

4.4.3 Spring驱动的JPA交易

强烈建议您阅读声明式事务管理(如果尚未阅读),以更详细地介绍Spring的声明式事务支持。

对于JPA,推荐的策略是通过JPA的本地交易支持进行本地交易。 Spring的JpaTransactionManager提供了许多本地JDBC事务中已知的功能(例如,特定于事务的隔离级别和资源级别的只读优化),而不需要任何常规JDBC连接池(无需XA)。

如果注册的JpaDialect支持检索基础JDBC连接,Spring JPA还允许已配置的JpaTransactionManager向访问同一数据源的JDBC访问代码公开JPA事务。 Spring为EclipseLink和Hibernate JPA实现提供了方言。有关JpaDialect机制的详细信息,请参见下一部分。

作为一种直接替代方案,Spring的本机HibernateTransactionManager能够与Spring Framework 5.1和Hibernate 5.2 / 5.3以及更高版本的JPA访问代码进行交互,以适应多种Hibernate规范并提供JDBC交互。结合LocalSessionFactoryBean设置,这特别有意义。有关详细信息,请参见用于JPA交互的本机Hibernate设置。

4.4.4 了解JpaDialect和JpaVendorAdapter

作为高级功能,JpaTransactionManager和AbstractEntityManagerFactoryBean的子类允许将自定义JpaDialect传递到jpaDialect Bean属性中。 JpaDialect实现通常可以通过特定于供应商的方式来启用Spring支持的以下高级功能:

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

  • 检索事务性JDBC连接(用于公开基于JDBC的DAO)

  • 将PersistenceExceptions高级转换为Spring DataAccessExceptions

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

作为主要用于Spring的全功能LocalContainerEntityManagerFactoryBean设置的更广泛的提供程序适应工具,JpaVendorAdapter将JpaDialect的功能与其他特定于提供程序的默认值结合在一起。指定HibernateJpaVendorAdapter或EclipseLinkJpaVendorAdapter分别是为Hibernate或EclipseLink自动配置EntityManagerFactory设置的最便捷方法。请注意,这些提供程序适配器主要是设计用于与Spring驱动的事务管理一起使用的(即,与JpaTransactionManager一起使用)。

有关其操作以及在Spring的JPA支持中如何使用它们的更多详细信息,请参见JpaDialect和JpaVendorAdapter javadoc。

4.4.5 使用JTA事务管理设置JPA

作为JpaTransactionManager的替代,Spring还允许通过JTA在Java EE环境中或与独立事务协调器(例如Atomikos)一起进行多资源事务协调。除了选择Spring的JtaTransactionManager而不是JpaTransactionManager之外,您还需要采取其他一些步骤:

  • 基础的JDBC连接池需要具有XA功能,并且必须与事务协调器集成在一起。这在Java EE环境中通常很简单,它通过JNDI公开了另一种数据源。有关详细信息,请参见您的应用程序服务器文档。类似地,一个独立的事务协调器通常带有特殊的XA集成的DataSource实现。再次,检查其文档。

  • 需要为JTA配置JPA EntityManagerFactory设置。这是特定于提供程序的,通常是通过将特殊属性指定为LocalContainerEntityManagerFactoryBean上的jpaProperties。对于Hibernate,这些属性甚至是特定于版本的。有关详细信息,请参见Hibernate文档。

  • Spring的HibernateJpaVendorAdapter强制执行某些面向Spring的默认值,例如连接释放模式,即合上,该模式与Hibernate 5.0中Hibernate自己的默认值匹配,但在5.1 / 5.2中则不再适用。对于JTA设置,请不要声明HibernateJpaVendorAdapter开头或关闭其prepareConnection标志。或者,将Hibernate 5.2的hibernate.connection.handling_mode属性设置为DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT以恢复Hibernate自己的默认设置。有关WebLogic的相关说明,请参见带有Hibernate的Spurious Application Server警告。

  • 或者,考虑从应用程序服务器本身(即通过JNDI查找而不是本地声明的LocalContainerEntityManagerFactoryBean)获取EntityManagerFactory。服务器提供的EntityManagerFactory可能在服务器配置中需要特殊定义(使部署的可移植性降低),但已针对服务器的JTA环境进行了设置。

4.4.6 JPA交互的本机Hibernate设置和本机Hibernate事务

从Spring Framework 5.1和Hibernate 5.2 / 5.3开始,与HibernateTransactionManager结合使用的本地LocalSessionFactoryBean设置允许与@PersistenceContext和其他JPA访问代码进行交互。现在,Hibernate SessionFactory本地实现JPA的EntityManagerFactory接口,而Hibernate Session句柄本身就是JPA EntityManager。 Spring的JPA支持工具会自动检测本地的Hibernate会话。

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

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

就像JPA LocalContainerEntityManagerFactoryBean一样,LocalSessionFactoryBean和LocalSessionFactoryBuilder支持后台引导。有关简介,请参见后台引导。

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