20. 对象关系 Map(ORM)数据访问

20.1 Spring 的 ORM 简介

Spring 框架支持与 Hibernate,Java Persistence API(JPA)和 Java 数据对象(JDO)集成,以进行资源 Management,数据访问对象(DAO)实现和事务策略。例如,对于 Hibernate 而言,它具有一流的支持以及一些便捷的 IoC 功能,可解决许多典型的 Hibernate 集成问题。您可以通过依赖注入为 O/R(对象关系)Map 工具配置所有受支持的功能。他们可以参与 Spring 的资源和事务 Management,并且符合 Spring 的通用事务和 DAO 异常层次结构。推荐的集成样式是针对普通的 Hibernate,JPA 和 JDO API 编写 DAO。不再推荐使用 Spring 的 DAO 模板的较旧样式。但是,有关此样式的说明,请参见附录中的第 39.1 节“经典 ORM 用法”

当您创建数据访问应用程序时,Spring 会为您选择的 ORM 层显着增强。您可以根据需要利用尽可能多的集成支持,并且应该将这种集成工作与内部构建类似基础架构的成本和风险进行比较。不管使用哪种技术,您都可以像使用库一样使用许多 ORM 支持,因为所有内容都是作为一组可重用的 JavaBean 设计的。 Spring IoC 容器中的 ORM 有助于配置和部署。因此,本节中的大多数示例都显示了 Spring 容器内部的配置。

使用 Spring 框架创建 ORM DAO 的好处包括:

  • 更轻松的测试. Spring 的 IoC 方法使交换 Hibernate SessionFactory实例,JDBC DataSource实例,事务 Management 器和 Map 对象实现(如果需要)的实现和配置位置变得容易。反过来,这使得隔离每个与持久性相关的代码的测试变得容易得多。

  • 通用数据访问异常. Spring 可以包装您的 ORM 工具中的异常,将其从专有(可能已检查)的异常转换为通用的运行时 DataAccessException 层次结构。此功能使您可以仅在适当的层中处理大多数不可恢复的持久性异常,而不会烦人样板捕获,抛出和异常声明。您仍然可以根据需要捕获和处理异常。请记住,JDBC 异常(包括特定于 DB 的方言)也将转换为相同的层次结构,这意味着您可以在一致的编程模型中使用 JDBC 执行某些操作。

  • 常规资源 Management. Spring 应用程序上下文可以处理 Hibernate SessionFactory实例,JPA EntityManagerFactory实例,JDBC DataSource实例和其他相关资源的位置和配置。这使得这些值易于 Management 和更改。 Spring 提供了对持久性资源的高效,便捷和安全的处理。例如,使用 Hibernate 的相关代码通常需要使用相同的 Hibernate Session,以确保效率和适当的事务处理。通过通过 Hibernate SessionFactory公开当前的Session,Spring 可以轻松地透明地创建Session并将其绑定到当前线程。因此,对于任何本地或 JTA 事务环境,Spring 都能解决许多典型的 Hibernate 使用问题。

  • *集成的事务 Management.*您可以通过@TransactionalComments 或通过在 XML 配置文件中显式配置事务 AOP 建议,使用声明性的,面向方面的编程(AOP)样式方法拦截器包装 ORM 代码。在这两种情况下,都为您处理事务语义和异常处理(回滚等)。如下所述,在资源和 TransactionManagement中,您还可以交换各种事务 Management 器,而不会影响与 ORM 相关的代码。例如,您可以在两种情况下使用相同的完整服务(例如声明式事务)在本地事务和 JTA 之间交换。此外,与 JDBC 相关的代码可以与您用于执行 ORM 的代码完全事务集成。这对于不适合 ORM 的数据访问非常有用,例如批处理和 BLOB 流,它们仍需要与 ORM 操作共享公共事务。

Tip

要获得更全面的 ORM 支持,包括对 MongoDB 等替代数据库技术的支持,您可能需要查看Spring Data项目套件。如果您是 JPA 用户,则https://spring.io使用 JPA 访问数据的入门指南提供了很好的介绍。

20.2 一般 ORM 集成注意事项

本节重点介绍适用于所有 ORM 技术的注意事项。 第 20.3 节“休眠”部分提供更多详细信息,并在具体上下文中显示这些功能和配置。

Spring ORM 集成的主要目标是使用任何数据访问和事务技术进行清晰的应用程序分层,以及松散耦合应用程序对象。不再有业务服务对数据访问或 Transaction 策略的依赖,不再有硬编码的资源查找,不再难以替换的单例,不再有自定义服务注册表。一种简单而一致的方法来连接应用程序对象,使它们可重复使用,并尽可能摆脱容器依赖性。所有单独的数据访问功能都可以单独使用,但可以与 Spring 的应用程序上下文概念很好地集成,从而提供基于 XML 的配置和对不需要 Spring 意识的纯 JavaBean 实例的交叉引用。在典型的 Spring 应用程序中,许多重要的对象是 JavaBean:数据访问模板,数据访问对象,事务 Management 器,使用数据访问对象和事务 Management 器的业务服务,Web 视图解析器,使用业务服务的 Web 控制器等等。 。

20.2.1 资源和 TransactionManagement

典型的业务应用程序中充斥着重复的资源 Management 代码。许多项目试图发明自己的解决方案,有时为了编程方便而牺牲了对故障的正确处理。 Spring 提倡简单的解决方案来进行适当的资源处理,即通过在 JDBC 情况下进行模板化以及为 ORM 技术应用 AOP 拦截器来实现 IoC。

基础结构提供适当的资源处理,并将特定的 API 异常适当地转换为未经检查的基础结构异常层次结构。 Spring 引入了 DAO 异常层次结构,适用于任何数据访问策略。对于直接 JDBC,上一节中提到的JdbcTemplate类提供了连接处理和SQLExceptionDataAccessException层次结构的正确转换,包括将特定于数据库的 SQL 错误代码转换为有意义的异常类。对于 ORM 技术,请参阅下一节以了解如何获得相同的异常转换好处。

在事务 Management 方面,JdbcTemplate类通过相应的 Spring 事务 Management 器挂接到 Spring 事务支持并支持 JTA 和 JDBC 事务。对于受支持的 ORM 技术,Spring 通过 Hibernate,JPA 和 JDO 事务 Management 器提供了 Hibernate,JPA 和 JDO 支持以及 JTA 支持。有关 Transaction 支持的详细信息,请参见第十七章,TransactionManagement章。

20.2.2 异常翻译

在 DAO 中使用 Hibernate,JPA 或 JDO 时,必须决定如何处理持久性技术的本机异常类。 DAO 会根据技术抛出HibernateExceptionPersistenceExceptionJDOException的子类。这些异常都是运行时异常,不必声明或捕获。您可能还必须处理IllegalArgumentExceptionIllegalStateException。这意味着调用者只能将异常视为一般致命的异常,除非他们想要依赖于持久性技术自身的异常结构。如果不将调用者与实现策略联系在一起,则无法捕获特定原因,例如乐观锁定失败。对于高度基于 ORM 和/或不需要任何特殊异常处理的应用程序,可以接受这种折衷方案。但是,Spring 允许通过@RepositoryComments 透明地应用异常转换:

@Repository
public class ProductDaoImpl implements ProductDao {

    // class body here...

}
<beans>

    <!-- Exception translation bean post processor -->
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

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

</beans>

后处理器自动查找所有异常翻译器(PersistenceExceptionTranslator接口的实现),并建议所有带有@Repository注解标记的 bean,以便发现的翻译器可以拦截并对抛出的异常应用适当的翻译。

总结:您可以基于纯持久性技术的 API 和 Comments 来实现 DAO,同时仍然受益于 SpringManagement 的事务,依赖项注入和对 Spring 的自定义异常层次结构的透明异常转换(如果需要)。

20.3 Hibernate

我们将从在 Spring 环境中介绍Hibernate 5开始,使用它来演示 Spring 用于集成 O/RMap 器的方法。本节将详细讨论许多问题,并显示 DAO 实现和事务划分的不同变体。这些模式中的大多数都可以直接转换为所有其他受支持的 ORM 工具。然后,本章的以下各节将介绍其他 ORM 技术,并在其中显示更简短的示例。

Note

从 Spring 4.0 开始,Spring 需要 Hibernate 3.6 或更高版本。请注意,Hibernate 团队 3 年前就停止了对 Hibernate 的支持,甚至于 2015 年底逐步淘汰了对 Hibernate 4.x 的支持。因此,从 2016 年的角度来看,我们建议使用 Hibernate 5.0 及更高版本。

20.3.1 在 Spring 容器中设置 SessionFactory

为了避免将应用程序对象与硬编码的资源查找绑定在一起,可以在 Spring 容器中将诸如 JDBC DataSource或 Hibernate SessionFactory之类的资源定义为 bean。需要访问资源的应用程序对象通过 bean 引用接收对此类 sched 义实例的引用,如下一节中的 DAO 定义所示。

XML 应用程序上下文定义的以下摘录显示了如何在其之上设置 JDBC DataSource和 Hibernate SessionFactory

<beans>

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource"/>
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.HSQLDialect
            </value>
        </property>
    </bean>

</beans>

从本地 Jakarta Commons DBCP BasicDataSource切换到位于 JNDI 的DataSource(通常由应用服务器 Management)只是配置问题:

<beans>
    <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>

您还可以使用 Spring 的JndiObjectFactoryBean/<jee:jndi-lookup>来访问位于 JNDI 的SessionFactory,以检索并公开它。但是,这通常在 EJB 上下文之外并不常见。

20.3.2 基于普通的 Hibernate API 实现 DAO

Hibernate 具有称为上下文会话的功能,其中,Hibernate 本身每个事务 Management 一个当前的Session。这大致相当于 Spring 对每个事务同步一个 Hibernate Session。基于普通的 Hibernate API,相应的 DAO 实现类似于以下示例:

public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Collection loadProductsByCategory(String category) {
        return this.sessionFactory.getCurrentSession()
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list();
    }
}

该样式与 Hibernate 参考文档和示例的样式类似,不同之处在于将SessionFactory保留在实例变量中。我们强烈建议在 Hibernate 的 CaveatEmptor 示例应用程序中的老式static HibernateUtil类上使用基于实例的设置。 (通常,除非绝对必要,否则不要在static变量中保留任何资源.)

上面的 DAO 遵循了依赖项注入模式:它很好地适合于 Spring IoC 容器,就像针对 Spring 的HibernateTemplate进行编码一样。当然,这种 DAO 也可以用纯 Java 设置(例如,在单元测试中)。只需实例化它,然后使用所需的工厂参考号调用setSessionFactory(..)。作为 Spring bean 的定义,DAO 类似于以下内容:

<beans>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

</beans>

这种 DAO 样式的主要优点是它仅依赖于 Hibernate API。不需要导入任何 Spring 类。从非侵入性的角度来看,这当然很吸引人,毫无疑问,对于 Hibernate 开发人员而言,它会变得更加自然。

但是,DAO 抛出普通的HibernateException(未经检查,因此不必声明或捕获),这意味着调用者只能将异常视为一般致命的-除非他们希望依赖于 Hibernate 自己的异常层次结构。如果不将调用者与实现策略联系在一起,则无法捕获特定原因,例如乐观锁定失败。这种权衡可能是基于 Hibernate 的应用程序和/或不需要任何特殊异常处理的应用程序可以接受的。

幸运的是,Spring 的LocalSessionFactoryBean支持任何 Spring 事务策略的 Hibernate 的SessionFactory.getCurrentSession()方法,即使使用HibernateTransactionManager,也返回当前的 SpringManagement 的事务Session。当然,该方法的标准行为仍然是与正在进行的 JTA 事务关联的当前Session的返回(如果有)。无论您使用的是 Spring 的JtaTransactionManager,EJB 容器 Management 的事务(CMT)还是 JTA,此行为均适用。

总结:您可以基于普通的 Hibernate API 实现 DAO,同时仍然能够参与 SpringManagement 的事务。

20.3.3 声明式事务划分

我们建议您使用 Spring 的声明式事务支持,该支持使您可以用 AOP 事务拦截器替换 Java 代码中的显式事务划分 API 调用。可以使用 JavaComments 或 XML 在 Spring 容器中配置此事务拦截器。这种声明式事务处理功能使您可以使业务服务免于重复的事务划分代码,并专注于添加业务逻辑,这是应用程序的 true 价值。

Note

在 continue 之前,强烈建议您阅读第 17.5 节“声明式事务 Management”(如果尚未阅读的话)。

您可以使用@TransactionalComments 对服务层进行 Comments,并指示 Spring 容器查找这些 Comments 并为这些带 Comments 的方法提供事务性语义。

public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    @Transactional
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDao.loadProductsByCategory(category);
        // ...
    }

    @Transactional(readOnly = true)
    public List<Product> findAllProducts() {
        return this.productDao.findAllProducts();
    }

}

您需要在容器中设置的是 Bean 的PlatformTransactionManager实现以及“<tx:annotation-driven/>”条目,并在运行时选择@Transactional处理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- SessionFactory, DataSource, etc. omitted -->

    <bean id="transactionManager"
            class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <tx:annotation-driven/>

    <bean id="myProductService" class="product.SimpleProductService">
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>

20.3.4 程序性 Transaction 划分

您可以在应用程序的更高级别中划分事务的边界,而这种低级别的数据访问服务则跨越了许多操作。对周围业务服务的实施也没有限制;它只需要一个 Spring PlatformTransactionManager。同样,后者可以来自任何地方,但最好通过setTransactionManager(..)方法作为 bean 的引用,就像productDAO应该由setProductDao(..)方法设置一样。以下代码片段显示了 Spring 应用程序上下文中的事务 Management 器和业务服务定义,以及业务方法实现的示例:

<beans>

    <bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager" ref="myTxManager"/>
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private TransactionTemplate transactionTemplate;
    private ProductDao productDao;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                List productsToChange = this.productDao.loadProductsByCategory(category);
                // do the price increase...
            }
        });
    }
}

Spring 的TransactionInterceptor允许将任何已检查的应用程序异常与回调代码一起引发,而TransactionTemplate仅限于回调中的未检查的异常。如果未检查的应用程序异常或应用程序将事务标记为仅回滚(通过TransactionStatus),则TransactionTemplate触发回滚。 TransactionInterceptor默认情况下的行为方式相同,但允许每个方法配置可回退策略。

20.3.5TransactionManagement 策略

TransactionTemplateTransactionInterceptor都将实际的事务处理委托给PlatformTransactionManager实例,对于 Hibernate 应用程序,该实例可以是HibernateTransactionManager(对于单个 Hibernate SessionFactory,使用ThreadLocal Session)或JtaTransactionManager(委托给容器的 JTA 子系统)。 。您甚至可以使用自定义PlatformTransactionManager实现。从本地 Hibernate 事务 Management 切换到 JTA(例如,面对某些应用程序部署的分布式事务要求时)仅是配置问题。只需用 Spring 的 JTA 事务实现替换 Hibernate 事务 Management 器。事务划分和数据访问代码都将保持不变,因为它们仅使用通用的事务 ManagementAPI。

对于跨多个 Hibernate 会话工厂的分布式事务,只需将JtaTransactionManager作为事务策略与多个LocalSessionFactoryBean定义组合即可。然后,每个 DAO 都将一个特定的SessionFactory引用传递到其相应的 bean 属性中。如果所有基础 JDBC 数据源都是事务性容器数据源,那么只要使用JtaTransactionManager作为策略,业务服务就可以在任意数量的 DAO 和任意数量的会话工厂之间划分事务。

HibernateTransactionManagerJtaTransactionManager都允许使用 Hibernate 进行正确的 JVM 级别的缓存处理,而无需特定于容器的事务 Management 器查找或 JCA 连接器(如果您不使用 EJB 来初始化事务)。

HibernateTransactionManager可以将 Hibernate JDBC Connection导出为特定的DataSource的普通 JDBC 访问代码。如果仅访问一个数据库,则此功能允许使用混合的 Hibernate 和 JDBC 数据访问进行完全的高级事务划分,而无需 JTA。如果已通过LocalSessionFactoryBean类的dataSource属性使用DataSource设置了传入的SessionFactory,则HibernateTransactionManager自动将 Hibernate 事务公开为 JDBC 事务。或者,您可以通过HibernateTransactionManager类的dataSource属性显式指定应该为其公开事务的DataSource

20.3.6 比较容器 Management 的资源和本地定义的资源

您可以在容器 Management 的 JNDI SessionFactory和本地定义的 JNDI SessionFactory之间切换,而不必更改应用程序代码的一行。将资源定义保留在容器中还是在应用程序中本地保留,主要取决于您使用的事务策略。与 Spring 定义的本地SessionFactory相比,手动注册的 JNDI SessionFactory没有任何好处。通过 Hibernate 的 JCA 连接器部署SessionFactory可以增加参与 Java EE 服务器的 Management 基础结构的附加价值,但不会增加实际价值。

Spring 的事务支持未绑定到容器。使用除 JTA 之外的任何其他策略配置,事务支持还可以在独立或测试环境中使用。尤其是在单数据库事务的典型情况下,Spring 的单资源本地事务支持是 JTA 的轻量级功能强大的替代方案。当您使用本地 EJBStateless 会话 Bean 驱动事务时,即使仅访问单个数据库,您也依赖于 EJB 容器和 JTA,并且仅使用 Stateless 会话 Bean 通过容器 Management 的事务来提供声明性事务。另外,以编程方式直接使用 JTA 都需要 Java EE 环境。就 JTA 本身和 JNDI DataSource实例而言,JTA 不仅仅涉及容器依赖项。对于非 Spring,JTA 驱动的 Hibernate 事务,您必须使用 Hibernate JCA 连接器,或将额外的 Hibernate 事务代码和TransactionManagerLookup配置为正确的 JVM 级别的缓存。

如果 Spring 驱动的事务正在访问单个数据库,则它们可以与本地定义的 Hibernate SessionFactory一起使用,也可以与本地 JDBC DataSource一起使用。因此,只有在已分配事务需求时,才需要使用 Spring 的 JTA 事务策略。 JCA 连接器需要特定于容器的部署步骤,并且显然首先需要 JCA 支持。与部署具有本地资源定义和 Spring 驱动的事务的简单 Web 应用程序相比,此配置需要更多的工作。另外,如果使用的是 WebLogic Express(不提供 JCA),则通常需要使用容器的企业版。具有本地资源和跨越单个数据库的事务的 Spring 应用程序可以在任何 Java EE Web 容器(没有 JTA,JCA 或 EJB)中运行,例如 Tomcat,Resin 甚至普通 Jetty。此外,您可以轻松地在桌面应用程序或测试套件中重用这样的中间层。

考虑到所有问题,如果您不使用 EJB,请坚持使用本地SessionFactory设置和 Spring 的HibernateTransactionManagerJtaTransactionManager。您将获得所有好处,包括适当的事务性 JVM 级别的缓存和分布式事务,而不会给容器部署带来不便。通过 JCA 连接器对 Hibernate SessionFactory进行 JNDI 注册仅在与 EJB 结合使用时才增加价值。

20.3.7 Hibernate 的虚假应用程序服务器警告

在某些具有严格XADataSource实现的 JTA 环境中(目前仅某些 WebLogic Server 和 WebSphere 版本),在配置 Hibernate 而不考虑该环境的 JTA PlatformTransactionManager对象的情况下,有可能在应用程序服务器日志中显示虚假警告或异常。 。这些警告或异常指示正在访问的连接不再有效,或者 JDBC 访问不再有效,这可能是因为事务不再有效。例如,这是 WebLogic 的实际异常:

java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No
further JDBC access is allowed within this transaction.

您只需使 Hibernate 知道将与其同步(与 Spring 同步)的 JTA PlatformTransactionManager实例即可解决此警告。您可以通过以下两种方式执行此操作:

  • 如果在您的应用程序上下文中,您已经直接获取 JTA PlatformTransactionManager对象(大概是通过JndiObjectFactoryBean<jee:jndi-lookup>从 JNDI 获取)并将其提供给例如 Spring 的JtaTransactionManager,那么最简单的方法是指定对定义此 JTA PlatformTransactionManager的 bean 的引用。实例作为LocalSessionFactoryBean. Spring 的jtaTransactionManager属性的值,然后使该对象可用于 Hibernate。

  • 您很有可能还没有 JTA PlatformTransactionManager实例,因为 Spring 的JtaTransactionManager可以自己找到它。因此,您需要配置 Hibernate 以直接查找 JTA PlatformTransactionManager。您可以通过在 Hibernate 配置中配置特定于应用程序服务器的TransactionManagerLookup类来实现此目的,如 Hibernate 手册中所述。

本节的其余部分描述了在 Hibernate 不了解 JTA PlatformTransactionManager的情况下发生的事件的 Sequences。

如果未配置 Hibernate 来识别 JTA PlatformTransactionManager,则在 JTA 事务提交时会发生以下事件:

  • JTA 事务提交。

  • Spring 的JtaTransactionManager已同步到 JTA 事务,因此 JTA 事务 Management 器通过* afterCompletion *回调对其进行了回调。

  • 除其他活动外,这种同步还可以通过 Spring 到 Hibernate 的afterTransactionCompletion回调(用于清除 Hibernate 缓存),然后在 Hibernate Session 上进行显式close()调用来触发 Hibernate 的回调,这将导致 Hibernate 尝试close() JDBC Connection。

  • 在某些环境中,此Connection.close()调用随后触发警告或错误,因为应用程序服务器不再认为Connection可用,因为事务已被提交。

当 Hibernate 配置为具有 JTA PlatformTransactionManager感知能力时,在 JTA 事务提交时会发生以下事件:

  • JTA 事务已准备好提交。

  • Spring 的JtaTransactionManager已同步到 JTA 事务,因此 JTA 事务 Management 器通过* beforeCompletion *回调回调了该事务。

  • Spring 知道 Hibernate 本身已同步到 JTA 事务,并且其行为与先前方案不同。假设 Hibernate Session完全需要关闭,Spring 将立即关闭它。

  • JTA 事务提交。

  • Hibernate 已同步到 JTA 事务,因此 JTA 事务 Management 器通过* afterCompletion *回调对该事务进行了回调,并且可以正确清除其缓存。

20.4 JDO

Spring 支持标准的 JDO 2.0 和 2.1 API 作为数据访问策略,遵循与 Hibernate 支持相同的样式。相应的集成类位于org.springframework.orm.jdo包中。

20.4.1 PersistenceManagerFactory 设置

Spring 提供了一个LocalPersistenceManagerFactoryBean类,允许您在 Spring 应用程序上下文中定义本地 JDO PersistenceManagerFactory

<beans>

    <bean id="myPmf" class="org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean">
        <property name="configLocation" value="classpath:kodo.properties"/>
    </bean>

</beans>

另外,您可以通过直接实例化PersistenceManagerFactory实现类来设置PersistenceManagerFactory。 JDO PersistenceManagerFactory实现类遵循 JavaBeans 模式,就像 JDBC DataSource实现类一样,它自然适用于使用 Spring 的配置。此设置样式通常支持传递给connectionFactory属性的 Spring 定义的 JDBC DataSource。例如,对于开源 JDO 实现 DataNucleus(以前为 JPOX)(http://www.datanucleus.org/),这是PersistenceManagerFactory实现的 XML 配置:

<beans>

 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
 </bean>

 <bean id="myPmf" class="org.datanucleus.jdo.JDOPersistenceManagerFactory" destroy-method="close">
    <property name="connectionFactory" ref="dataSource"/>
    <property name="nontransactionalRead" value="true"/>
 </bean>

</beans>

您还可以通常通过特定 JDO 实现提供的 JCA 连接器在 Java EE 应用程序服务器的 JNDI 环境中设置 JDO PersistenceManagerFactory。 Spring 的标准JndiObjectFactoryBean<jee:jndi-lookup>可用于检索和公开此类PersistenceManagerFactory。但是,在 EJB 上下文之外,在 JNDI 中保留PersistenceManagerFactory并没有 true 的好处:仅出于充分的理由选择这样的设置。有关讨论,请参见第 20.3.6 节“比较容器 Management 的资源和本地定义的资源”;那里的论点也适用于 JDO。

20.4.2 基于纯 JDO API 实现 DAO

通过使用注入的PersistenceManagerFactory,也可以直接针对普通 JDO API 编写 DAO,而无需任何 Spring 依赖项。以下是相应的 DAO 实现的示例:

public class ProductDaoImpl implements ProductDao {

    private PersistenceManagerFactory persistenceManagerFactory;

    public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
        this.persistenceManagerFactory = pmf;
    }

    public Collection loadProductsByCategory(String category) {
        PersistenceManager pm = this.persistenceManagerFactory.getPersistenceManager();
        try {
            Query query = pm.newQuery(Product.class, "category = pCategory");
            query.declareParameters("String pCategory");
            return query.execute(category);
        }
        finally {
            pm.close();
        }
    }
}
<beans>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="persistenceManagerFactory" ref="myPmf"/>
    </bean>

</beans>

这种 DAO 的主要问题在于,它们总是从工厂得到一个新的PersistenceManager。要访问由 SpringManagement 的事务PersistenceManager,请在目标PersistenceManagerFactory的前面定义一个TransactionAwarePersistenceManagerFactoryProxy(包含在 Spring 中),然后将对该代理的引用传递到 DAO 中,如以下示例所示:

<beans>

    <bean id="myPmfProxy"
            class="org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy">
        <property name="targetPersistenceManagerFactory" ref="myPmf"/>
    </bean>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="persistenceManagerFactory" ref="myPmfProxy"/>
    </bean>

</beans>

您的数据访问代码将从其调用的PersistenceManagerFactory.getPersistenceManager()方法接收事务性PersistenceManager(如果有)。后一种方法调用通过代理进行,该代理首先检查当前的事务PersistenceManager,然后再从工厂获得新的事务。如果是事务性PersistenceManager,则会忽略PersistenceManager上的任何close()调用。

如果您的数据访问代码始终在活动事务中运行(或至少在活动事务同步中运行),则可以安全地省略PersistenceManager.close()调用,从而省略整个finally块,可以这样做,以确保 DAO 实现的简洁性:

public class ProductDaoImpl implements ProductDao {

    private PersistenceManagerFactory persistenceManagerFactory;

    public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
        this.persistenceManagerFactory = pmf;
    }

    public Collection loadProductsByCategory(String category) {
        PersistenceManager pm = this.persistenceManagerFactory.getPersistenceManager();
        Query query = pm.newQuery(Product.class, "category = pCategory");
        query.declareParameters("String pCategory");
        return query.execute(category);
    }
}

对于依赖于活动事务的 DAO,建议您通过关闭TransactionAwarePersistenceManagerFactoryProxy's `allowCreate标志来强制执行活动事务:

<beans>

    <bean id="myPmfProxy"
            class="org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy">
        <property name="targetPersistenceManagerFactory" ref="myPmf"/>
        <property name="allowCreate" value="false"/>
    </bean>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="persistenceManagerFactory" ref="myPmfProxy"/>
    </bean>

</beans>

这种 DAO 样式的主要优点是,它仅依赖于 JDO API。不需要导入任何 Spring 类。从非侵入性的角度来看,这当然很有吸引力,并且对于 JDO 开发人员来说可能更自然。

但是,DAO 抛出普通的JDOException(未经检查,因此不必声明或捕获),这意味着调用者只能将异常视为致命的,除非您想依赖 JDO 自己的异常结构。如果不将调用者与实现策略联系在一起,则无法捕获特定原因,例如乐观锁定失败。这种权衡对于完全基于 JDO 和/或不需要任何特殊异常处理的应用程序可能是可接受的。

总之,您可以基于普通的 JDO API 进行 DAO,并且它们仍然可以参与 SpringManagement 的事务。如果您已经熟悉 JDO,则此策略可能会吸引您。但是,此类 DAO 会抛出普通的JDOException,并且您必须显式转换为 Spring 的DataAccessException(如果需要)。

20.4.3TransactionManagement

Note

强烈建议您阅读第 17.5 节“声明式事务 Management”(如果尚未这样做的话),以更详细地介绍 Spring 的声明式事务支持。

要在事务中执行服务操作,可以使用 Spring 的通用声明式事务工具。例如:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="myTxManager" class="org.springframework.orm.jdo.JdoTransactionManager">
        <property name="persistenceManagerFactory" ref="myPmf"/>
    </bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="productDao" ref="myProductDao"/>
    </bean>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="increasePrice*" propagation="REQUIRED"/>
            <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/>
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="productServiceMethods"
                expression="execution(* product.ProductService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
    </aop:config>

</beans>

JDO 需要活动事务来修改持久对象。与 Hibernate 相反,JDO 中不存在非事务刷新概念。因此,您需要为特定环境设置所选的 JDO 实现。具体来说,您需要为 JTA 同步进行显式设置,以检测活动的 JTA 事务本身。对于由 Spring 的JdoTransactionManager执行的本地事务,这不是必需的,但无论是由 Spring 的JtaTransactionManager还是由 EJB CMT 和纯 JTA 驱动,都必须参与 JTA 事务。

JdoTransactionManager能够将 JDO 事务公开给访问相同 JDBC DataSource的 JDBC 访问代码,只要注册的JdoDialect支持底层 JDBC Connection的检索。默认情况下,基于 JDBC 的 JDO 2.0 实现就是这种情况。

20.4.4 JdoDialect

作为一项高级功能,LocalPersistenceManagerFactoryBeanJdoTransactionManager都支持可传递到jdoDialect bean 属性的自定义JdoDialect。使用JdoDialect实现,通常可以以特定于供应商的方式启用 Spring 支持的高级功能:

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

  • 检索事务性 JDBC Connection以暴露给基于 JDBC 的 DAO

  • 应用查询超时,这是从 SpringManagement 的事务超时中自动计算得出的

  • 急于刷新PersistenceManager,以使事务更改对基于 JDBC 的数据访问代码可见

  • JDOExceptions到 Spring DataAccessExceptions的高级翻译

有关其操作以及如何在 Spring 的 JDO 支持中使用它们的更多详细信息,请参见JdoDialect javadocs。

20.5 JPA

可以在org.springframework.orm.jpa软件包下获得的 Spring JPA 以与 Hibernate 或 JDO 的集成相似的方式为Java 持久性 API提供全面的支持,同时知道底层实现以提供其他功能。

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

Spring JPA 支持提供了三种设置 JPA EntityManagerFactory的方式,应用程序将使用它们来获取实体 Management 器。

LocalEntityManagerFactoryBean

Note

仅在简单的部署环境(例如独立应用程序和集成测试)中使用此选项。

LocalEntityManagerFactoryBean创建一个EntityManagerFactory,适用于应用程序仅使用 JPA 进行数据访问的简单部署环境。工厂 bean 使用 JPA PersistenceProvider自动检测机制(根据 JPA 的 Java SE 自举),并且在大多数情况下,仅要求您指定持久性单元名称:

<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

Note

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

从 JNDI 获得EntityManagerFactory(例如在 Java EE 环境中),只需更改 XML 配置即可:

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

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

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

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

LocalContainerEntityManagerFactoryBean

Note

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

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>

Note

<exclude-unlisted-classes/>快捷方式指示应该对被 Comments 的实体类进行* no *扫描。指定的显式“ true”值<exclude-unlisted-classes>true</exclude-unlisted-classes/>也表示不进行扫描。 <exclude-unlisted-classes>false</exclude-unlisted-classes/>确实触发了扫描;但是,如果要进行实体类扫描,建议仅省略exclude-unlisted-classes元素。

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

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

When is load-time weaving required?

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

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

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

有关LoadTimeWeaver实现及其设置的更多信息,请参考 AOP 章节中的称为“ Spring 配置”的部分,这些实现是通用的或针对各种平台(例如 Tomcat,WebLogic,GlassFish,Resin 和 JBoss)定制的。

如前所述,您可以使用context:load-time-weaver XML 元素的@EnableLoadTimeWeavingComments 配置上下文范围内的LoadTimeWeaver。所有 JPA LocalContainerEntityManagerFactoryBeans都会自动拾取这样的全局编织器。这是设置加载时间编织器的首选方法,可自动检测平台(WebLogic,GlassFish,Tomcat,Resin,JBoss 或 VM 代理),并将编织器自动传播到所有可识别编织器的 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 转换器仅在类加载器级别应用,因此彼此隔离。

处理多个持久性单元

例如,对于依赖于多个持久性单元位置的应用程序,这些应用程序存储在 Classpath 中的各种 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>

默认实现允许对PersistenceUnitInfo实例进行自定义,然后再将其声明给 JPA 提供者,该声明通过其属性影响所有*托管单元,或者通过PersistenceUnitPostProcessor以编程方式通过PersistenceUnitPostProcessor(允许持久性单元选择)进行定制。如果未指定PersistenceUnitManager,则LocalContainerEntityManagerFactoryBean在内部创建和使用一个。

20.5.2 基于 JPA 的 DAO:EntityManagerFactory 和 EntityManager

Note

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

通过使用注入的EntityManagerFactoryEntityManager,可以针对不带任何 Spring 依赖关系的普通 JPA 编写代码。如果启用了PersistenceAnnotationBeanPostProcessor,Spring 可以在字段和方法级别理解@PersistenceUnit@PersistenceContextComments。使用@PersistenceUnitComments 的普通 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 利用 Comments 的优势要求注入默认的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 context:annotation-config XML 元素。这样做会自动注册所有 Spring 标准后处理器以进行基于 Comments 的配置,包括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();
    }
}

@PersistenceContextComments 具有可选属性type,默认属性为PersistenceContextType.TRANSACTION。此默认设置是您需要接收共享的 EntityManager 代理的条件。替代PersistenceContextType.EXTENDED是完全不同的事情:这导致所谓的扩展 EntityManager,它不是线程安全的,因此不能在并发访问的组件(例如 SpringManagement 的 Singleton bean)中使用。扩展的 EntityManager 仅应用于有状态的组件(例如,驻留在会话中),并且 EntityManager 的生命周期不依赖于当前事务,而完全取决于应用程序。

Method- and field-level Injection

指示依赖项注入的 Comments(例如@PersistenceUnit@PersistenceContext)可以应用于类内的字段或方法,因此可以使用* method-level injection field-level injection *表达式。字段级 Comments 简洁明了,易于使用,而方法级 Comments 则允许对注入的依赖项进行进一步处理。在这两种情况下,成员的可见性(公共,受保护,私有)都不重要。

那么类级 Comments 呢?

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

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

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

20.5.3 Spring 驱动的 JPA 事务

Note

强烈建议您阅读第 17.5 节“声明式事务 Management”(如果尚未这样做的话),以更详细地介绍 Spring 的声明式事务支持。

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

Spring JPA 还允许已配置的JpaTransactionManager将 JPA 事务公开给访问相同DataSource的 JDBC 访问代码,只要注册的JpaDialect支持底层 JDBC Connection的检索。 Spring 提供了开箱即用的 EclipseLink,Hibernate 和 OpenJPA JPA 实现方言。有关JpaDialect机制的详细信息,请参见下一部分。

20.5.4 JpaDialect 和 JpaVendorAdapter

作为高级功能,JpaTransactionManagerAbstractEntityManagerFactoryBean的子类支持自定义JpaDialect,该自定义JpaDialect可以传递到jpaDialect bean 属性中。 JpaDialect实现可以启用 Spring 支持的一些高级功能,通常以特定于供应商的方式:

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

  • 检索事务性 JDBC Connection以暴露给基于 JDBC 的 DAO)

  • PersistenceExceptions到 Spring DataAccessExceptions的高级翻译

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

Tip

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

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

20.5.5 使用 JTA 事务 Management 设置 JPA

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

  • 底层 JDBC 连接池必须具有 XA 功能,并与事务协调器集成。这在 Java EE 环境中通常很简单,只需通过 JNDI 公开另一种DataSource即可。检查您的应用程序服务器文档以了解详细信息。类似地,独立的事务协调器通常带有特殊的 XA 集成的DataSource实现。再次,检查其文档。

  • 需要为 JTA 配置 JPA EntityManagerFactory设置。这是特定于提供程序的,通常通过在LocalContainerEntityManagerFactoryBean上将特殊属性指定为“ jpaProperties”。对于 Hibernate,这些属性甚至是特定于版本的。请查看您的 Hibernate 文档以获取详细信息。

  • Spring 的HibernateJpaVendorAdapter强制执行某些面向 Spring 的默认值,例如连接释放模式“ on-close”,它与 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 的相关说明,请参见第 20.3.7 节“带有 Hibernate 的虚假应用程序服务器警告”

  • 或者,考虑从应用程序服务器本身获取EntityManagerFactory,即通过 JNDI 查找而不是本地声明的LocalContainerEntityManagerFactoryBean。服务器提供的EntityManagerFactory在您的服务器配置中可能需要特殊的定义,这使得部署的可移植性较差,但是将为服务器的 JTA 环境进行开箱即用的设置。