欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring ORM数据访问——JPA

程序员文章站 2022-05-23 15:11:23
...

JPA

Spring JPA在org.springframework.orm.jpa包中已经可用,Spring JPA用了Hibernate集成相似的方法来提供更易于理解的JPA支持,与此同时,了解了JPA底层实现,可以理解更多的Spring JPA特性。

Spring中JPA配置的三个选项

Spring JPA支持提供了三种配置JPAEntityManagerFactory的方法,之后通过EntityManagerFactory来获取对应的实体管理器。

LocalEntityManagerFactoryBean

通常只有在简单的部署环境中使用此选项,例如在独立应用程序或者进行集成测试时,才会使用这种方式。

LocalEntityManagerFactoryBean创建一个适用于应用程序且仅使用JPA进行数据访问的简单部署环境的EntityManagerFactory。工厂bean会使用JPAPersistenceProvider自动检测机制,并且在大多数情况下,仅要求开发者指定持久化单元的名称:

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
    </bean>
</beans>

这种形式的JPA部署是最简单的,同时限制也很多。开发者不能引用现有的JDBCDataSource bean定义,并且不支持全局事务。而且,持久化类的织入(weaving)(字节码转换)是特定于提供者的,通常需要在启动时指定特定的JVM代理。该选项仅适用于符合JPA Spec的独立应用程序或测试环境。

从JNDI中获取EntityManagerFactory

在部署到J2EE服务器时可以使用此选项。检查服务器的文档来了解如何将自定义JPA提供程序部署到服务器中,从而对服务器进行比默认更多的个性化定制。

从JNDI获取EntityManagerFactory(例如在Java EE环境中),只需要在XML配置中加入配置信息即可:

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

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

在这种情况下,整个持久化单元部署(包括持久化类的织入(weaving)(字节码转换))都取决于J2EE服务器。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之外使用自定义数据源并控制织入(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设置选项,允许在应用程序中进行灵活的本地配置。它支持连接到现有的JDBCDataSource,支持本地和全局事务等。但是,它对运行时环境施加了需求,其中之一就是如果持久性提供程序需要字节码转换,就需要有织入(weaving)能力的类加载器。

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

何时需要加载时间织入?
并非所有JPA提供商都需要JVM代理。Hibernate就是一个不需要JVM代理的例子。如果开发者的提供商不需要代理或开发者有其他替代方案,例如通过定制编译器或Ant任务在构建时应用增强功能,则不用使用加载时间编织器。

LoadTimeWeaver是一个Spring提供的接口,它允许以特定方式插入JPAClassTransformer实例,这取决于环境是Web容器还是应用程序服务器。 通过代理挂载ClassTransformers通常性能较差。代理会对整个虚拟机进行操作,并检查加载的每个类,这是生产服务器环境中最不需要的额外负载。

Spring为各种环境提供了一些LoadTimeWeaver实现,允许ClassTransformer实例仅适用于每个类加载器,而不是每个VM。

有关LoadTimeWeaver的实现及其设置的通用或定制的各种平台(如Tomcat,WebLogic,GlassFish,Resin和JBoss)的更多了解,请参阅AOP章节中的Spring配置一节。

如前面部分所述,开发者可以使用@EnableLoadTimeWeaving注解或者load-time-weaverXML元素来配置上下文范围的LoadTimeWeaver。所有JPALocalContainerEntityManagerFactoryBeans都会自动拾取这样的全局织入器。这是设置加载时间织入器的首选方式,为平台(WebLogic,GlassFish,Tomcat,Resin,JBoss或VM代理)提供自动检测功能,并将织入组件自动传播到所有可以感知织入者的Bean:

<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    ...
</bean>

开发者也可以通过LocalContainerEntityManagerFactoryBeanloadTimeWeaver属性来手动指定专用的织入器:

<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作为*仓库来避免可能昂贵的持久性单元发现过程。默认实现允许指定多个位置,这些位置将通过持久性单元名称进行解析并稍后检索。(默认情况下,搜索classpath下的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>

在默认实现传递给JPA provider之前,是允许通过属性(影响全部持久化单元)或者通过PersistenceUnitPostProcessor以编程(对选择的持久化单元进行)进行对PersistenceUnitInfo进行自定义的。如果没有指定PersistenceUnitManager,则由LocalContainerEntityManagerFactoryBean在内部创建和使用。

基于JPA的EntityManagerFactory和EntityManager来实现DAO

虽然EntityManagerFactory实例是线程安全的,但EntityManager实例不是。注入的JPA EntityManager的行为类似于从JPA Spec中定义的应用程序服务器的JNDI环境中提取的EntityManager。它将所有调用委托给当前事务的EntityManager(如果有);否则,它每个操作返回的都是新创建的EntityManager,通过使用不同的EntityManager来保证使用时的线程安全。

通过注入的方式使用EntityManagerFactoryEntityManager来编写JPA代码,是不需要依赖任何Spring定义的类的。如果启用了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) {
        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();
            }
        }
    }
}

上面的DAO对Spring的实现是没有任何依赖的,而且很适合与Spring的应用程序上下文进行集成。而且,DAO还可以通过注解来注入默认的EntityManagerFactory

<beans>

    <!-- bean post-processor for JPA annotations -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>

如果不想明确定义PersistenceAnnotationBeanPostProcessor,可以考虑在应用程序上下文配置中使用Spring上下文annotation-configXML元素。这样做会自动注册所有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注解具有可选的属性类型,默认值为PersistenceContextType.TRANSACTION。此默认值是开发者所需要接收共享的EntityManager代理。替代方案PersistenceContextType.EXTENDED则完全不同:该方案会返回一个所谓扩展的EntityManager,该EntityManager不是线程安全的,因此不能在并发访问的组件(如Spring管理的单例Bean)中使用。扩展实体管理器仅应用于状态组件中,比如持有会话的组件,其中EntityManager的生命周期与当前事务无关,而是完全取决于应用程序。

方法和实例变量级别注入
指示依赖注入(例如@PersistenceUnit@PersistenceContext)的注解可以应用于类中的实例变量或方法,也就是表达式方法级注入和实例变量级注入。实例变量级注释简洁易用,而方法级别允许进一步处理注入的依赖关系。在这两种情况下,成员的可见性(publicprotectedprivate)并不重要。
类级注解怎么办?
在J2EE平台上,它们用于依赖关系声明,而不是资源注入。

注入的EntityManager是由Spring管理的(Spring可以意识到正在进行的事务)。重要的是要注意,因为通过注解进行注入,即使新的DAO实现使用通过方法注入的EntityManager而不是EntityManagerFactory的注入的,在应用程序上下文XML中不需要进行任何修改。

这种DAO风格的主要优点是它只依赖于Java Persistence API;不需要导入任何Spring的实现类。而且,Spring容器可以识别JPA注解来实现自动的注入和管理。从非侵入的角度来看,这种风格对JPA开发者来说可能更为自然。

Spring驱动的JPA事务

如果开发者还没有阅读声明式事务管理,强烈建议开发者先行阅读,这样可以更详细地了解Spring的对声明式事务支持。

JPA的推荐策略是通过JPA的本地事务支持的本地事务。 Spring的JpaTransactionManager提供了许多来自本地JDBC事务的功能,例如针对任何常规JDBC连接池(不需要XA要求)指定事务的隔离级别和资源级只读优化等。

Spring JPA还允许配置JpaTransactionManager将JPA事务暴露给访问同一DataSource的JDBC访问代码,前提是注册的JpaDialect支持检索底层JDBC连接。Spring为EclipseLink和Hibernate JPA实现提供了实现。有关JpaDialect机制的详细信息,请参阅下一节。

JpaDialect和JpaVendorAdapter

作为高级功能,JpaTransactionManagerAbstractEntityManagerFactoryBean的子类支持自定义JpaDialect,将其作为Bean传递给jpaDialect属性。JpaDialect实现可以以供应商特定的方式使能Spring支持的一些高级功能:

  • 应用特定的事务语义,如自定义隔离级别或事务超时
  • 为基于JDBC的DAO导出事务性JDBC连接
  • PersistenceExceptions到SpringDataAccessExceptions的异常转义

这对于特殊的事务语义和异常的高级翻译特别有价值。但是Spring使用的默认实现(DefaultJpaDialect)是不提供任何特殊功能的。如果需要上述功能,则必须指定适当的方言才可以。

作为一个更广泛的供应商适应设施,主要用于Spring的全功能LocalContainerEntityManagerFactoryBean设置,JpaVendorAdapterJpaDialect的功能与其他提供者特定的默认设置相结合。指定HibernateJpaVendorAdapterEclipseLinkJpaVendorAdapter是分别为Hibernate或EclipseLink自动配置EntityManagerFactory设置的最简单方便的方法。但是请注意,这些提供程序适配器主要是为了与Spring驱动的事务管理一起使用而设计的,即为了与JpaTransactionManager配合使用的。

有关其操作的更多详细信息以及在Spring的JPA支持中如何使用,请参阅JpaDialectJpaVendorAdapter的Javadoc。

为JPA配置JTA事务管理

作为JpaTransactionManager的替代方案,Spring还允许通过JTA在J2EE环境中或与独立的事务协调器(如Atomikos)进行多资源事务协调。除了用Spring的JtaTransactionManager替换JpaTransactionManager,还有需要以下一些操作:

  • 底层JDBC连接池是需要具备XA功能,并与开发者的事务协调器集成的。这在J2EE环境中很简单,只需通过JNDI导出不同类型的DataSource即可。有关导出DataSource等详细信息,可以参考应用服务器文档。类似地,独立的事务协调器通常带有特殊的XA集成的DataSource实现。
  • 需要为JTA配置JPAEntityManagerFactory。这是特定于提供程序的,通常通过在LocalContainerEntityManagerFactoryBean的特殊属性指定为”jpaProperties”。在使用Hibernate的情况下,这些属性甚至是需要基于特定的版本的;请查阅Hibernate文档以获取详细信息。
  • Spring的HibernateJpaVendorAdapter会强制执行某些面向Spring的默认设置,例如在Hibernate 5.0中匹配Hibernate自己的默认值的连接释放模式“on-close”,但在5.1 / 5.2中不再存在。对于JTA设置,不要声明HibernateJpaVendorAdapter开始,或关闭其prepareConnection标志。或者,将Hibernate 5.2的hibernate.connection.handling_mode属性设置为DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT以恢复Hibernate自己的默认值。有关WebLogic的相关说明,请参考Hibernate的虚假应用服务器警告一节。
  • 或者,可以考虑从应用程序服务器本身获取EntityManagerFactory,即通过JNDI查找而不是本地声明的LocalContainerEntityManagerFactoryBean。服务器提供的EntityManagerFactory可能需要在服务器配置中进行特殊定义,减少了部署的移植性,但是EntityManagerFactory将为开箱即用的服务器JTA环境设置。