14. 对象关系映射(ORM)数据访问

14.1 介绍 Spring 中的 ORM

Spring Framework 支持集成 Hibernate, Java Persistence API (JPA) 和 Java Data Objects (JDO) 用于资源管理、数据访问对象(DAO)的实现,和事务策略。例如,对于 Hibernate 有一流的支持,使用方便的 IoC 特性,解决许多典型的 Hibernate 集成问题。您可以通过依赖注入配置的所有 O/R (对象关系) 映射工具的特性。他们可以参与 Spring 的资源和事务管理,他们符合 Spring 的通用事务和 DAO 异常层次结构。建议集成风格是在 Hibernate, JPA, 和 JDO APIs 中使用 DAO 。旧的 Spring DAO 模板不再推荐使用;然而,关于风格的论述可以见 Section 31.1, “Classic ORM usage” .

当你创建数据访问应用时,Spring 能对你选择的 ORM 层进行显着的增强。你根据你的需求进行尽可能多的集成支持,同时需要比这种整合在建一个类似的基础设施时的内部风险。通过库,你可以使用很多的 ORM 的支持,不用管技术,因为一切都是设计成一组可重用的 JavaBean。ORM 在 Spring IoC 容器便于配置和部署。因此,本文中的大多数示例配置显示在 Spring 容器里。

使用 Spring Framework 来创建您的 ORM DAO 好处包括:

  • 更容易测试. Spring 的 IoC 方法便于交换实现和配置 Hibernate SessionFactory 实例,JDBC DataSource(数据源)实例,事务管理,以及映射对象实现(如果需要)。这反过来使其更容易隔离测试每一块持续相关代码。
  • 常见的数据访问异常. Spring 可以从你的 ORM 工具包装异常,将他们从专有的(可能检查)异常转为共同运行时 DataAccessException 层次。这个特性允许您处理大多数持久化的异常不可恢复的,而且只出现在适当的层,没有恼人的捕捉、抛出、和异常声明。当然根据需要你仍然可以捕获和处理异常。记住,JDBC 异常(包括数据库特殊的方言)也转换为相同的层次结构,这意味着您可以在使用 JDBC 执行一些操作时拥有一致的编程模型。
  • 通用资源管理. Spring 应用程序上下文可以处理的定位和配置Hibernate SessionFactory 实例,JPA EntityManagerFactory实例,JDBC DataSource(数据源)实例,和其他相关资源。这使得这些值易于管理和改变。Spring 提供了高效、简单和安全处理持久性的资源。例如,关联代码来使用 Hibernate ,通常需要使用相同的 Hibernate Session,以确保高效和适当的事务处理。Spring 很容易创建和透明地将Session绑定到当前线程,通过使用 Hibernate SessionFactory 来暴露出当前Session。因此,针对任何本地或 JTA 事务环境,Spring 解决许多在典型的 Hibernate 使用中的惯性问题。
  • 集成事务管理. 可以通过声明式的,面向方面的编程(AOP)风格方法拦截器包装你的 ORM 代码通过吗,可以采用@Transactional注释或通过显式配置事务 AOP advice 在一个 XML 配置文件中。在这两种情况下,都是可以为您处理事务语义和异常处理(回滚,等等)。如下面所讨论的 Resource and transaction management, 你也可以交换不同的事务管理器,而不影响 ORM 相关的代码。例如,您可以在相同的全面服务(如声明性事务)的两个场景,实现本地事务和 JTA 之间交换。另外,JDBC 相关的代码可以完全集成事务到你使用的 ORM 代码中。这是对于数据访问有用,但不适合 ORM 中的批处理和 BLOB 流等,这仍然需要在ORM操作 交换共同的事务。
[Tip]Tip

为更全面了解的 ORM 的支持,包括支持替代数据库技术,如 MongoDB 数据库,您可能希望查看 Spring Data 配套的项目。如果你是一个 JPA 用户 Getting Started Accessing Data with JPA 提供了一个很好的介绍.

14.2 常见的 ORM 集成方面的注意事项

本节强调的 注意事项应用与所有的 ORM 技术。 Section 14.3, “Hibernate” 这节提供了更多细节,同时展示了这些特性和具体上下文的配置。

Spring 的 ORM 集成的主要目标是明确的应用程序分层,包括在任何数据访问、事务技术和松耦合的应用程序对象。没有更多的业务服务依赖于数据访问或事务策略,不再资源查找硬编码,不再强制替换单例,没有更多的自定义服务注册。一个简单的和一致的方法来连接应用程序对象,让他们尽可能的对容器依赖是可以重用并且是自由。所有个人数据访问特性可用的,但可以与Spring 应用程序上下文的概念集成,提供基于 xml 的配置和交叉引用的普通 JavaBean 实例而不需要 Spring-aware(Spring 的意识)。在一个典型的 Spring 应用程序中,许多重要的对象是 JavaBean:数据访问模板,数据访问对象,事务管理器,使用数据访问对象和事务管理器的业务服务, web 视图解析器,使用业务服务的 web 控制器,等等。

14.2.1 资源和事务管理

典型的商业应用是用重复的资源管理代码杂乱的堆积起来的。很多项目试图创造自己的解决方案,有时为了编程方便来牺牲故障的处理。Spring 提倡简单的处理适当资源的方案,即在JDBC的案例 IoC 通过模板和在 ORM 技术应用 AOP 拦截器。

基础设施提供适当的资源处理,并且能将特定的 API 异常适当的转换为一个未检查的基础的异常层次结构。Spring 引入了 DAO 异常层次结构,适用于任何的数据访问策略。对于直接的 JDBC,在前一节提到的JdbcTemplate类提供了连接处理和将 SQLException 适当的转换为DataAccessException 层次结构,其中包括将 具体数据库 SQL 错误代码转为有意义的异常类。对于 ORM 技术,见下一节,如何得到相同异常转换的好处。

当涉及到事务管理,JdbcTemplate类与 Spring 的事务支持挂钩,并且通过各自的 Spring 事务管理器支持 JTA 和 JDBC 事务。为支持 ORM 技术 Spring 提供了通过与 JTA 支持类似的 Hibernate,JPA,和 JDO 事务管理器来实现对 Hibernate,JPA 和 JDO 支持。更多事务的支持,详细信息,参 Chapter 11, 事务管理 chapter.

14.2.2 异常转化

当你在 DAO 中使用 Hibernate、JPA 或 JDO 时,你必须决定如何处理持久化技术的原生异常类。运用不同的技术,DAO 会抛出HibernateExceptionPersistenceExceptionJDOException 的子类。这些异常都是运行时的异常,不需要声明或捕获。你可能必须处理IllegalArgumentExceptionIllegalStateException。这意味着调用者只能将异常处理成为一个通常为致命的问题,除非他们想要依赖于持久化技术自身的异常结构。捕捉乐观锁定失败等具体原因是不可能的除非把调用者与实现策略相联系。取消这交换是可接受的对于应用程序是基于 ORM 和/或 不需要任何特殊的异常处理。然而,Spring 通过 @Repository 注解 来使异常透明的转化:

@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>

postprocessor 会自动寻找所有异常转换器( 实现 PersistenceExceptionTranslator 接口),建议所有 bean 标有@Repository注解,以便发现的转换器可以在抛出异常时拦截和应用适当的转换。

总之:您可以实现 DAO 基于纯持久化技术的API和注解,同时仍然受益于Spring 管理事务,依赖注入、和透明将异常转换(如果需要)为 Spring 的自定义的异常层次结构。

14.3 Hibernate

现在要开始谈下 Spring 环境中的 Hibernate 3 用它来展示 Spring 对集成 O/R 映射的方法。本节将详细讨论许多问题,并显示不同的 DAO 实现和事务界定。大多数这些模式可以直接从所有其他支持 ORM 的工具中转换。以下部分在本章将覆盖其他的 ORM 技术,并显示简短的例子。

[Note]Note

对于Spring 4.0, Spring 需要 Hibernate 3.6 或者更高版本

14.3.1 在 Spring 容器中设置 SessionFactory

为了避免应用程序对象与硬编码的资源查找想绑定,您可以定义资源如 JDBC DataSource 或者 Hibernate SessionFactory 为 Spring 容器的 bean。应用对象需要通过对 bean 的引用来访问资源接受引用,就如在下一节中说明的 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.hibernate3.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-located DataSource, 主要的配置为:

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

您还可以访问 JNDI-located SessionFactory,使用 Spring 的JndiObjectFactoryBean/<jee:jndi-lookup> 来检索和暴露他。然而,通常在 EJB 环境外不常用。

14.3.2 基于平常的 Hibernate 3 API 来实现 DAO

Hibernate 3 有一个特性称为上下文会话,Hibernate 本身在每个事务管理一个当前 Session 。这是大致相当于 Spring 的 每个事务一个当前 Hibernate Session的同步。相应的 DAO 实现像下面的例子,基于普通Hibernate API

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 支持 Hibernate SessionFactory.getCurrentSession()方法用于任何 Spring 事务策略,返回当前 Spring 管理的事务 Session即使是HibernateTransactionManager.。当然,这种方法的标准行为返回仍然是当前Session与持续的JTA事务有关。这种行为适用于不管您使用是Spring 的 JtaTransactionManager,EJB容器管理的事务(CMT),或 JTA。

总之:你可以基于平常的 Hibernate 3 API 来实现 DAO,同时仍然能够参与 Spring 管理事务。

14.3.3 声明式事务划分

建议你使用 Spring 声明式事务的支持,这使您能够代替显式事务划分 API调用 AOP 事务拦截器中的 Java 代码。这个事务拦截器可以配置 Spring容器通过使用 Java 注释或 XML。这个声明式事务能力允许您保持业务服务中的重复性事务划分代码码更自由,并且让你只关注添加业务逻辑,而这是您的应用程序的真正价值。

[Note]Note

在继续之前,强烈建议你读Section 11.5, “Declarative transaction management” 如果你还没有这样做

此外,事务语义比如传播行为和隔离水平可以在配置文件的改变,不影响业务服务的实现。

下面的示例说明如何使用 XML 配置 AOP 事务拦截器,一个简单的服务类:

<?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.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

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

    <tx:advice id="txAdvice" transaction-manager="myTxManager">
        <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>

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

</beans>

下面是要处理的服务类

public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

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

    // notice the absence of transaction demarcation code in this method
    // Spring's declarative transaction infrastructure will be demarcating
    // transactions on your behalf
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDao.loadProductsByCategory(category);
        // ...
    }
}

我们还展示了一个基于配置属性的支持,在下面的例子中。你通过 @Transactional注释的服务层,并引导 Spring 容器找到这些注释,这些注释的方法提供事务性语义。

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

}

正如你可以看到下面的配置实例,配置更加简化,与上述 XML 实例,同时还提供了在服务层的代码注释驱动相同的功能。所有您需要提供的是TransactionManager 的实现和"<tx:annotation-driven/>" 实体

<?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.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <tx:annotation-driven/>

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

</beans>

14.3.4 编程式事务划分

可以在高级的应用中划分事务,在这样的底层数据访问服务生成任意数量的操作。限制也不存在于周边业务服务的实现;它只需要一个 Spring PlatformTransactionManager。再次,后者可以来自任何地方,但最好是通过 setTransactionManager(..)方法来对 bean 引用,正如由setProductDao(..)方法来设置 productDAO。下面的代码片段显示了在Spring 应用程序上下文中定义一个事务管理器和业务服务,以及一个业务方法实现:

<beans>

    <bean id="myTxManager" class="org.springframework.orm.hibernate3.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)标记为 rollback-only(只回滚) ,TransactionTemplate触发回滚事务。默认时 TransactionInterceptor表现是一样的,但是允许在每个方法中配置回滚策略。

14.3.5 事务管理策略

TransactionTemplateTransactionInterceptor代表了对 PlatformTransactionManager 实例的实际事务处理,在 Hibernate 的应用中它们可以是一个 HibernateTransactionManager(一个Hibernate 的 SessionFactory,在引擎下使用的是ThreadLocal Session)或 JtaTransactionManager(委派到容器的 JTA 子系统)。你甚至可以使用一个自定义的PlatformTransactionManager 实现。从原生 Hibernate 事务管理 转到 JTA,如你的某些应用程序部署具有分布式事务处理的要求,那么这仅仅是一个配置的问题,只要将 Hibernate 事务管理简单的替换为 Spring 的 JTA 即可。两个的事务划分和数据访问代码的不用改变,因为他们只是使用了通用的事务管理 API。

对于分布式事务跨多个 Hibernate 会话工厂,只要简单地把JtaTransactionManager 与具有多个定义的LocalSessionFactoryBean组合成为一个事务策略。每个 DAO 得到一个特定的SessionFactory的引用传递到其相应的 bean 属性。如果所有底层的 JDBC 数据源的事务容器,业务服务可以划分事务到任意数量的DAO 和任何数量的会话工厂,而这无需没有特殊的处理,只要是使用JtaTransactionManager策略。

<beans>

    <jee:jndi-lookup id="dataSource1" jndi-name="java:comp/env/jdbc/myds1"/>

    <jee:jndi-lookup id="dataSource2" jndi-name="java:comp/env/jdbc/myds2"/>

    <bean id="mySessionFactory1"
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource1"/>
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.MySQLDialect
                hibernate.show_sql=true
            </value>
        </property>
    </bean>

    <bean id="mySessionFactory2"
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource2"/>
        <property name="mappingResources">
            <list>
                <value>inventory.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.OracleDialect
            </value>
        </property>
    </bean>

    <bean id="myTxManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

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

    <bean id="myInventoryDao" class="product.InventoryDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory2"/>
    </bean>

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

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

    <tx:advice id="txAdvice" transaction-manager="myTxManager">
        <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>

</beans>

HibernateTransactionManagerJtaTransactionManager允许适当的 JVM 级别的 Hibernate 缓存处理,而不需要容器特定的事务管理查找或 JCA 连接器(如果你不使用 EJB 启动事务)。

HibernateTransactionManager可以为一个特定的DataSource导出 Hibernate JDBC Connection到普通 JDBC 访问代码。此功能允许高级的混合 Hibernate 和 JDBC 数据访问的完全没有 JTA 的事务划分,如果你只访问一个数据库。如果你有设置通过 LocalSessionFactoryBean类的dataSource属性传入SessionFactoryDataSourceHibernateTransactionManager自动暴露 Hibernate 事务作为一个 JDBC 事务。或者,您可以明确指定DataSource中哪些事务是需要支持通过 HibernateTransactionManager 类的dataSource属性暴露的。

14.3.6 比较容器管理和本地定义的资源

你可以在一个容器管理的 JNDI SessionFactory和本地定义的互相切换,而无需更改应用程序的代码。是否保持资源定义在容器或本地应用程序中,主要取决于使用的事务策略。对比 Spring 定义的本地的SessionFactory,手动注册 JNDI SessionFactory没有任何好处。部署一个通过 Hibernate JCA 连接器的SessionFactory来提供 Java EE 服务器的管理基础设施的附加值,但在这之前不增加任何实际的值。

Spring 的事务支持不是绑定到一个容器中。在配置除了 JTA 以外的任何策略后,事务支持同样也能在一个独立的或测试的环境中工作。特别是在单独的数据库事务的典型应用中。Spring 是一个轻量级的单资源本地事务支持和强大的 JTA 的替代品。当你使用本地 EJB 无状态会话 bean 来驱动事务,你都必须要依赖 EJB 容器和 JTA,即使你只访问一个数据库,并且只使用无状态会话 bean 通过容器管理的事务来提供声明式事务。另外,直接使用 JTA 编程需要一个 Java EE 环境。JTA 并不只涉及容器依赖性的JTA 本身和 JNDI DataSource 实例。对于 非 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 级别缓存和分布式事务,没有容器部署的不便。JNDI 通过 JCA 连接器注册 Hibernate SessionFactory,在与 EJB 一起使用时只是增加了值。

14.3.7 在 Hibernate 中的虚假应用服务器告警

在一些 JTA 的非常严格的 XADataSource实现的环境中 — 目前只在一些 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 知道 JTA PlatformTransactionManager实例,它将同步(连同 Spring)。实现这个有两个选项:

  • 如果在你的应用程序上下文中你已经直接获取 JTA PlatformTransactionManager对象(大概是从 JNDI 通过JndiObjectFactoryBean<jee:jndi-lookup>)将它提供给,例如,Spring 的 JtaTransactionManager,那么最简单的方法是通过引用定义了这个 JTA PlatformTransactionManager实例的 bean给LocalSessionFactoryBean指定一个jtaTransactionManager的属性值。那么 Spring 就会使对象在 Hibernate 中可用
  • 你很有可能没有 JTA PlatformTransactionManager实例,因为Spring 的 JtaTransactionManager 本身可以找到它。因此,你需要配置 Hibernate 直接查找 JTA PlatformTransactionManager。你可以在 Hibernate 配置中通过配置应用程序服务器特定的ransactionManagerLookup类实现这个,正如 Hibernate 手册所描述的那样

本节的其余部分描述了事件发生的顺序和 Hibernate 对 JTA PlatformTransactionManager的认知。

当 Hibernate 没有配置任何 JTA PlatformTransactionManager 的认知时,当一个 JTA 事务提交时以下事件发生:

  • JTA 事务提交。
  • Spring 的 JtaTransactionManager与 JTA 事务同步时,通过 JTA 事务管理执行一个 afterCompletion 的回调。
  • 在其他活动中,从 Spring 到 Hibernate 的同步可以触发回调,通过Hibernate 的 afterTransactionCompletion回调(用于清除 Hibernate 缓存),随后的是一个显式 close() 调用在 Hibernate Session,,导致 Hibernate 试图 close() JDBC 连接。
  • 在某些环境中,这个 Connection.close()调用然后触发警告或错误,因为应用程序服务器不再认为Connection是可用的,因为事务已经提交了。

当Hibernate 配置了 JTA PlatformTransactionManager的认知,当一个JTA事务提交,以下事件发生:

  • JTA 事务准备提交。
  • Spring 的 JtaTransactionManager 跟 JTA 事务是同步的,所以通过 JTA 事务管理器的 beforeCompletion回调来执行事务的回调。
  • Spring 感知到 Hibernate 本身与 JTA 事务是同步的,并且行为不同于在前面的场景。假设需要 Hibernate Session关闭, 那么 Spring将会关闭它。
  • JTA 事务提交
  • Hibernate 与 JTA 事务是同步的,所以通过 JTA 事务管理器的 beforeCompletion回调来执行事务的回调,并能正确清楚其缓存。

14.4 JDO

Spring 支持标准的 JDO 2.0 和 2.1 API 的数据访问策略,按照与 Hibernate 同样的支持方式。相应的集成类驻留在org.springframework.orm.jdo 包。

14.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实现类遵循 JavaBean 模式,就像一个JDBC DataSource 的实现类,这是在 Spring 里配置使用是非常合适的。这种设置方式通常支持一个 Spring 定义的JDBC DataSource,传递给 connectionFactory。例如,对于开源的 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>

也可以在 Java EE 应用服务的 JNDI 环境中 设置 JDO PersistenceManagerFactory,通常是通过 JCA 连接器提供包含 JDO 的实现。Spring 的标准中 JndiObjectFactoryBean<jee:jndi-lookup>可以用来检索和暴露比如PersistenceManagerFactory。然而,在 EJB 上下文 之外,没有真正的存在于在 JNDI 中保持 PersistenceManagerFactory :只选择这样的一个设置是一个很好的理由。请参见 Section 14.3.6, “比较容器管理和本地定义的资源” for a discussion; 讨论;那里的论点适用于JDO。

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

因为上面的 DAO 依赖注入模式,它适合在 Spring 容器中,就像在Spring 的 JdoTemplate 中编码:

<beans>

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

</beans>

这样的 DAO 主要的问题是,他们总是从工厂获得一个新的PersistenceManager。为了访问 Spring 管理的事务 PersistenceManager,需要定义一个TransactionAwarePersistenceManagerFactoryProxy(包含在Spring 中)在你的目标 PersistenceManagerFactory 面前,然后传递一个那个代理的引用到你的 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,任何 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 依来活动的事务,所有建议您通过关闭TransactionAwarePersistenceManagerFactoryProxyallowCreate 标签来强制激活事务:

<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 ,他们仍然可以参与 Spring管理事务。这策略会可能会吸引你如果你已经熟悉了 JDO。然而,这样的DAO 抛出平常的 JDOException,您必须显式地转换为 Spring 的DataAccessException(如果需要)。

14.4.3 Transaction management

[Note]Note

如果你还没有看过 Section 11.5, “Declarative transaction 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 需要一个活动的事务来修改持久化的对象。非事务性的刷新概念并不存在于 JDO,相对于 Hibernate。为此,你需要为特定的环境设置选择的JDO 的实现。具体来说,你需要设置明确的 JTA 同步,来检测一个活跃的JTA 事务本身。这对于 Spring 的 JdoTransactionManager执行的本地事务来说是没有必要的,但有必要参与 JTA 事务,不管是由 Spring JtaTransactionManager驱动 还是 EJB CMT 和普通的 JTA。

JdoTransactionManager能够使 JDO 事务 JDBC 访问代码f访问同一个JDBC`DataSource`,提供注册的 JdoDialect 支持底层的 JDBC ` Connection`检索。这是默认情况下基于JDBC 的 JDO 2.0实现

14.4.4 JdoDialect

作为一个高级功能,JdoTemplateJdoTransactionManager支持自定义JdoDialect可以传递到JdoDialect的 bean 属性。在这个场景中,DAO 不接受 PersistenceManagerFactory的引用,而是一个完整的JdoTemplate实例(例如,传递到JdoDaoSupport的属性JdoTemplate 中)。使用JdoDialect实现,您可以启用 Spring 的高级特性支持,通常特定于供应商的方式:

  • 应用于特定的事务语义,如自定义隔离级别或事务超时
  • 检索事务性的 JDBC Connection,用来暴露基于 JDBC 的 DAO
  • 应用查询超时,自动从 Spring 管理事务超时进行计算
  • 及时刷新 PersistenceManager,使事务变化对于基于 JDBC 的数据访问代码可见
  • JDOExceptions 向 Spring DataAccessExceptions的高级转换

查看 JdoDialect 的 javadocs 获取更多如果使用 Spring JDO 的细节

14.5 JPA

Spring JPA,存在与 org.springframework.orm.jpa 包,提供方便的对于 Java Persistence API 的类似于 Hibernate 或者 JDO 的支持,为了解底层的实现,提供额外的功能。

14.5.1 三种设置选项

Spring JPA 提供三种方式来设置 JPA EntityManagerFactory 用于应用程序实现实体的管理。

LocalEntityManagerFactoryBean

[Note]Note

只在简单部署环境中,比如独立的应用程序和集成测试才使用该选项

LocalEntityManagerFactoryBean 创建了一个仅使用 JPA 访问数据适合部署在简单环境下的应用程序的 EntityManagerFactory。工厂 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]Note

当部署在 Java EE 5 服务器中使用该选项,查看你的服务器的文档来获知如何部署自定义的 JPA 提供者 在你的服务器中,允许不同于服务器默认的提供者。

从 JNDI 中获得 EntityManagerFactory (举例 在 Java EE 5 环境中),只需简单配置 XML:

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

这个动作指定标准的 Java EE 5 的引导: Java EE服务器自动检测持久单元(实际上,META-INF/persistence.xml文件在应用的 jar 中)和在Java EE部署描述符中的 persistence-unit-ref 的实体(例如,web.xml)并为这些定义环境命名上下文的位置。

在这种情况下,整个持久化单元的部署,包括织入(字节码转换)持久化类,到 Java EE 服务器。JDBC DataSource是通过 JNDI 位置定义在META-INF/persistence.xml文件中。EntityManager 事务集成在服务器的 JTA 子系统中。Spring 只是使用获得的 EntityManagerFactory,通过依赖注入传递给应用程序对象,并且为持久单元管理事务,通常是通过 JtaTransactionManager

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

LocalContainerEntityManagerFactoryBean

[Note]Note

在基于 Spring 的使用 JPA 全功能的应用环境中,使用该选项。这个包含了 web 容器你比如 Tomcat 作为具有复杂的持续性要求的单独的应用和集成测试

LocalContainerEntityManagerFactoryBeanEntityManagerFactory完全控制配置和按需定制细粒度的适合的环境。LocalContainerEntityManagerFactoryBean创建基于 ` persistence.xml`文件 的 PersistenceUnitInfo的实例,提供dataSourceLookup的策略,指定loadTimeWeaver。因此可以在 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]Note

<exclude-unlisted-classes/>快捷表明应该出现未扫描的注解的实体类。一个明确的 true 值指定<exclude-unlisted-classes>true</exclude-unlisted-classes/> 也意味着没有扫描。<exclude-unlisted-classes>false</exclude-unlisted-classes/> d触发扫描;然而,它是建议干脆省略 exclude-unlisted-classes元素如果你想扫描产生的实体类。

使用 LocalContainerEntityManagerFactoryBean 是最强大的 JPA 设置选项,允许丰富的在应用中本地配置。它支持连接到现有的 JDBC DataSource,支持 包括 本地和全局的事务,等等。然而,它还对运行时环境的有特殊的需求,比如需要 weaving-capable(可织入的)的类载入器,当持久性提供者要求字节码转换时。

此选项可能 Java EE 5 服务器中内置 JPA 功能冲突。在一个完整的 Java EE 5 的环境下,考虑从 JNDI 获取你的EntityManagerFactory。另外,LocalContainerEntityManagerFactoryBean定义中指定一个自定义 persistenceXmlLocation ,例如, META-INF/my-persistence.xml ,并且只包含一个描述符,这个名字在你的应用程序 jar 文件。因为 Java EE 5 服务器只查找默认META-INF/persistence.xml文件,它忽略了这些自定义持久性单元,从而避免与 Spring 驱动的 JPA 预先设置冲突。(例如,这适用于 Resin 3.1)。

LoadTimeWeaver接口是一个 Spring 类,允许将 JPA ClassTransformer实例插入一个特定的方式,这取决于环境是 web 容器或应用程序服务器。通过一个 agent 来挂钩 ClassTransformers通常是无效的。代理工作在整个虚拟机并检查每一个加载类,通常是在生产服务器环境中不受欢迎的。

Spring 提供了许多 LoadTimeWeaver 各种环境的实现,允许ClassTransformer实例仅适用于每个类装入器,而不是每个 VM。

参考 AOP 章节 the section called “Spring configuration” 了解关于LoadTimeWeaver实现及其设置,包括泛型或定制各种平台(如 Tomcat、WebLogic、GlassFish、Resin 和JBoss)。

如上述所述部分,您可以配置一个context-wide(宽泛上下文的) LoadTimeWeaver使用context:load-time-weaver元素中的@EnableLoadTimeWeaving注释。这样一个全球织入是所有 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 转换器只在类装入器级别,因此彼此是隔离。

处理多个持久单元

对于依赖于多个持久单元的位置的应用程序,在类路径中,存储在不同的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内部创建和使用。

14.5.2 基于平常 JPA的 DAO 的实现

[Note]Note

虽然 EntityManagerFactory 实例是线程安全的 , 但 EntityManager 不是。注入的 JPA EntityManager 的行为像一个 从应用服务器的 JNDI 环境中通过 JPA 规范定义的 EntityManager 。它代表所有调用当前事务EntityManager,如果是的话;否则,它在每次操作时返回新创建的EntityManager,使其线程安全。

通过注入EntityManagerFactoryEntityManager,对于编写平常 JPA 代码对 Spring 没有任何依赖。 Spring 可以理解@PersistenceUnit@PersistenceContext和注解在字段和方法层面,如果启动 PersistenceAnnotationBeanPostProcessor的话。普通的JPA DAO实现使用 @PersistenceUnit注解可能看起来像这样:

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 context:annotation-config 在你的应用程序环境配置。这样做自动注册所有的 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 的生命周期不依赖于当前事务,而是完全取决于应用程序。

注入EntityManager是 Spring 管理的(意识到正在进行的事务)。需要注意的是,尽管新的 DAO 实现使用一个 EntityManager 方法注入而不是一个EntityManagerFactory,在应用程序上下文的 XML 注释的用法无需改变。

这种 DAO 风格的主要优点是,它不仅取决于 Java Persistence API;(Java 持久性API),而无需引进任何 Spring 的类。此外,作为 JPA 注释更容易理解,注解可以被 Spring 容器自动应用。这是从非侵袭性的的角度看很具有吸引力,对于 JPA 的开发人员来说可能感觉更自然。

14.5.3 事务管理

[Note]Note

如果你还没有看过 Section 11.5, “Declarative transaction 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.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="myEmf"/>
    </bean>

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

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

    <tx:advice id="txAdvice" transaction-manager="myTxManager">
        <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>

</beans>

Spring 的 JPA 允许配置 JpaTransactionManager来暴露 JPA 事务给 JDBC 访问代码从而能够访问同一个 JDBC DataSource提供注册JpaDialect支持底层的 JDBC Connection检索。开箱即用,Spring 提供了 TopLink ,Hibernate 和 OpenJPA 的 JPA 实现的方言。请参阅下一节 JpaDialect机制。

14.5.4 JpaDialect

作为一个高级功能 JpaTemplate,JpaTransactionManager `和`AbstractEntityManagerFactoryBean子类支持自定义 JpaDialect,传递到 JpaDialect bean 属性。在这种情况下,DAO 未得到EntityManagerFactory的引用,而是一个完整的JpaTemplate实例(例如,传递到JpaDaoSupportJpaTemplate 属性)。JpaDialect实现可以使一些 Spring 支持的高级功能,通常取决于特定供应商的方式:

  • 应用特定的事务语义,如自定义隔离级别或事务超时)
  • 检索事务暴露于基于 JDBC 的 DAO 的 JDBC Connection)
  • PersistenceExceptions 到 Spring DataAccessExceptions的高级转换

这对于特殊事务语义和高级的异常转换来说是非常有价值的。默认实现使用(DefaultJpaDialect)不提供任何特殊功能,如果需要上面的功能,你必须指定适当的方言。

查看 JpaDialect 的 javadocs 获取更多如果使用 Spring JPA 的细节