11. 事务管理

11.1 Spring Framework事务管理介绍

广泛的事务支持是Spring Framework吸引人们使用的原因之一. Spring Framework提供的对事务的一致性抽象 的好处体现在如下方面:

  • 在不同的API之间使用一致的编程模型, 如Java Transaction API (Java事务API, JTA), JDBC, Hibernate, Java Persistence API (Java持久化API, JPA), 以及 Java Data Objects (JDO).
  • 支持声明式事务管理.
  • 可编程事务管理比复杂的API, 如JTA,提供更简单的API.
  • 与Spring的数据访问层抽象的完美集成.

下面的章节向你介绍Spring Framework的事务价值提升和技术. (本章同样囊括了最佳实践, 应用集成, 和对 常见问题的解决方案.)

11.2 Spring Framework的事务支持模型的优点

传统上, Java EE 开发者在事务管理上有两个选择:全局 或者 本地 事务, 每一个都有严重的局限性. 关于全局和本地事务管理的介绍将在接下来的两节给出, 就在讨论Spring Framework的事务管理支持是怎样弥补 这些限制之后.

11.2.1 全局事务

全局性事务可以让你混合使用事务资源,传统关系型数据库以及消息队列. 应用服务器管理事务是通过JTA,他的API 用起来很笨重(来源于他的异常处理模型). 另外, 一个 JTA 的UserTransaction 通常需要由JNDI提供, 意味着 你在使用JTA的同时 需要使用JNDI. 很明显,当JTA是应用服务器上唯一可用的全局事务时,使用它会限制你 对应用代码的重用.

原先, 较为提倡的使用全局事务的方式是使用EJB 的CMT(容器管理事务,Container Managed Transaction): CMT 是一种 声明式事务管理 (区别于编程式事务管理)的形式. EJB CMT 移除了和事务相关的JNDI 发现, 虽然使用EJB本身就是需要JNDI的. 他移除了大多数,但不是所有的需要编写Java代码来管理事务. CMT和 JTA以及某一种应用服务器环境的绑定已经呈现下降趋势了. 当然, 这只有在你计划通过EJB来实现业务逻辑, 或者 至少是通过EJB处理事务的时候有效. EJB整体的不足是那么明显,这是一个无争议的话题,尤其是在面临着声明式 事务管理有令人信服的替代品的时候.

11.2.2 本地事务

本地事务是针对特定资源的, 例如一个和JDBC链接绑定在一起的事务. 本地事务有可能用起来更简单, 但是有一个 明显的弊端:他们不能运行在跨多种资源的事务中. 举一个例子, 一个管理使用JDBC连接的事务的代码不能和使用 全局JTA事务一起运行. 因为应用服务器并不专注于事务管理, 所以它并不能有助于确保跨多种资源事务的正确性. (记住,更糟的是大多数应用使用的是一个单一的事务源.) 另外的一个不好之处是本地事务都是对编程模型有 侵略性的.

11.2.3 Spring Framework的一致性编程模型

Spring解决了全局和本地事务的弊端. 它使得应用开发人员在任何环境中使用一致的编程模型成为可能. 你只需要编写代码一次, 然后你的代码就可以在不同的环境中适用于不同的事务管理. Spring Framework同时 提供了声明式事务管理和编程式事务管理.大多数开发人员更愿意使用声明式事务管理, 这也是推荐的方式.

通过编程式事务管理, 开发人员通过与Spring Framework的事务管理抽象工作, 实现可以运行于任何底层的 事务管理. 通过建议的声明式模型, 开发人员通常只会写少量或者不写和事务管理相关的代码, 并且从此不会依赖于 Spring Framework或者其他的事务API.

11.3 理解 Spring Framework 的事务抽象

Spring事物抽象的关键就是事务策略的概念. 事务策略在接口 org.springframework.transaction.PlatformTransactionManager中定义:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(
            TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

这是一个主要的的服务提供接口(service provider interface,SPI), 尽管它可以在你的应用中以 编程式的方式使用. 因为PlatformTransactionManager是一个 接口, 所以它就很容易在必要时被更换或者修改. 它没有和任何一种发现策略绑定,如JNDI. PlatformTransactionManager的实现定义起来就和Spring Framework的控制反转(IoC)容器中其他的 object(或者 bean)一样. 这个特性使得Spring Framework的事务管理是一个很有用的抽象即使是你用它来和 JTA一起使用的时候. 有关事务的代码测试起来也比直接使用JTA更加简单.

又一项符合Spring哲学的是, 可以由PlatformTransactionManager接口的任何方法抛出来的 TransactionException异常的类型是未检查的(unchecked)(这是说, 它继承自 java.lang.RuntimeException类的). 事务管理底层的失败往往都是致命的. 在一些罕见的情况下,应用的代码 是忽略事务的失败的, 这时应用的开发人员仍然可以有选择地捕获并处理TransactionException. 关键是 开发者没有被强迫那么做.

返回一个TransactionStatus对象的getTransaction(..)方法需TransactionDefinition参数. 返回的 TransactionStatus可能代表了一个新的事务, 也有可能是代表了一个已经存在的事务, 如果在调用栈中有 已经存在的事务相匹配的话. 后者的一种例子就是, 在Java EE的事务上下文中有一个TransactionStatus和 执行线程相关.

接口TransactionDefinition的定义:

  • 独立性(Isolation): 定义当前事务和其他工作的事务独立的程度. 例如, 当前事务能否感知到其他事务 未提交的写请求?
  • 传播性(Propagation): 通常, 在一个事务的范围内执行的代码也都在那个事务里面执行. 然而, 你可以 拥有定义当一个事务相关的方法在一个事务上下文已经存在时的行为. 例如, 代码可以在已经存在的事务中继续 运行(通常情况都是); 或者将那已经存在的事务挂起然后新建一个事务. Spring提供所有和EJB CMT相似的 事务传播属性选项. 要了解和事务相关的传播属性的语义描述, 请查看Section 11.5.7, “事务传播性”.
  • 超时(Timeout): 事务多长时间会计时归零并自动调用底层事务单元进行回滚.
  • 只读状态(Read-only status): 一个设置了只读的事务可以在你的代码需要读但是却不会修改数据的时候 使用. 只读的事务在进行用例优化的时候很有用, 例如当你使用Hibernate的时候.

这些设置都是和标准的事务相关的概念相关联的. 如有必要, 参考讨论事务独立等级和其他的有关事务的概念的资源. 理解这些概念是使用Spring Framework或者其他任何事务管理解决方案的基础.

接口TransactionStatus提供了一个事务相关代码操控事务执行和查询事务状态的简单方法. 它的定义也很熟悉, 就和其他所有的事务管理API一样:

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

不管你是在Spring中使用声明式还是编程式事务管理, 定义正确的PlatformTransactionManager的实现都是 绝对正确的. 你通常会通过依赖注入的方式定义实现.

PlatformTransactionManager的实现通常需要知道他们所运行的环境是什么: JDBC, JTA, Hibernate, 等等. 下面的例子向你展示了怎样定义一个本地的PlatformTransactionManager实现. (这个例子运行在纯JDBC 环境中.)

你可以这样定义一个使用JDBC的DataSource

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

相关联的PlatformTransactionManager实例定义将会引用这个DataSource的定义. 它看起来将像这样:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

如果你实在一个Java EE的环境中使用JTA将使用容器中的DataSource, 通过JNDI来获取, 将和Spring的 JtaTransactionManager结合起来. 这就是为什么使用JTA和JNDI的版本看起来像这样:

<?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:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

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

    <!-- other <bean/> definitions here -->

</beans>

JtaTransactionManager不需要知道DataSource或者其他的关于数据源的定义, 因为它直接使用容器的 全局事务管理基础.

[Note]Note

上面的关于dataSource的定义使用了来自jee命名空间的<jndi-lookup/>标签. 要了解更多的关于基于 schema的配置, 请查看Chapter 33, XML Schema-based configuration, 如果要了解更多关于<jee/>的标签的信息请查看 Section 33.2.3, “the jee schema”.

你也可以想下面的例子一样非常方便地使用Hibernate的本地事务. 在这种情况下, 你需要定义一个Hibernate的 LocalSessionFactoryBean, 你的程序代码将通过它来获取Hibernate的Session的实例.

关于DataSource的定义就和前面的使用本地JDBC的例子中的定义一样, 这里也就不再列举了.

[Note]Note

如果被任何的无JTA事务管理使用的DataSource是通过JNDI发现得到并且受Java EE容器管理的, 那么它应该 是一个无事务的, 因为Spring Framework将管理事务, 而不是Java EE容器.

名称为txManager的bean在这种时候是HibernateTransactionManager类型的. 就和创建 DataSourceTransactionManager实例需要DataSource实例的引用一样,创建 HibernateTransactionManager实例需要对SessionFactory实例的引用.

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
</bean>

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

如果你是在使用Hibernate以及Java EE容器管理的JTA事务, 那么你应该简单的使用之前JTA的JDBC示例代码 里面的JtaTransactionManager.

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
[Note]Note

如果你使用JTA , 那你的事务管理声明就会看起来和底层数据访问的技术无关,不管他们是JDBC, Hibernate JPA 或者其他的何种受支持的技术. 这其实是因为JTA的事务是全局事务, 将会适配所有的事务型资源.

在这所有的例子中,应用的代码都不需要变动. 你可以只通过更改事务配置来改变事务的管理, 即使这种改变是 对应着事务从局部移动到全局或者反过来.

11.4 在事务中同步资源

现在你应该已经知道怎么创建不同的事务管理器了, 并且已经知道了他们是怎样将需要与事务同步的资源联系起来 的(例如DataSourceTransactionManager对应一个JDBC的DataSource, HibernateTransactionManager 对应一个Hibernate的SessionFactory, 等等). 本部分讲解程序代码如何直接或者间接地使用持久化API, 如JDBC, Hibernate, 或者JDO, 并且确保所需要的资源都被正确的创建、重用、清理. 本部分也会泰伦事务 同步机制之怎样通过和PlatformTransactionManager的关联触发(可选的)的.

11.4.1 高级同步方法

首选的方法是使用基于Spring的和持久化集成的API高级模板,或者使用原生的ORM API, 应用于事务支持型工厂bean 或者管理原生资源的工厂的代理. 这些事务型解决方案内建对资源创建、重用、清理、资源的可选事务同步以及 异常的映射的支持. 这样用户的数据访问代码就可以不再关心定位任务, 专心于非样板化的持久化逻辑. 通常, 你使用原生的ORM API或者使用样板化的方法来进行JDBC访问的话, 是使用JdbcTemplate. 这个解决方式 还会在本参考文档的后续章节中详细介绍.

11.4.2 低级的同步方法

DataSourceUtils (JDBC), EntityManagerFactoryUtils (JPA),SessionFactoryUtils(Hibernate), PersistenceManagerFactoryUtils (JDO), 等等这些类都是属于低级方法中的.当你的代码想要直接使用那 有关本地持久化事务API的时候, 你需要让这些类明确Spring Framework框架管理的实例已经得到了,事务已经 同步好了(可选的),并且异常运行中的异常也都会映射到一个一致的API.

例如, 在JDBC的例子中, 在DataSource中代替传统的JDBC中的getConnection()方法, 赢回感兴趣使用 Spring的org.springframework.jdbc.datasource.DataSourceUtils类,就像下面这样:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果存在一个已经和他同步(已连接)的事务, 那就返回它. 否则, 方法就会激发一个触发器创建一个新的连接, 并且是(可选的)与任何存在的事务同步的, 并且已经准备好在接下来在相同的事务中重用. 就像提到的那样, 所有 的SQLException都会被包装成Spring Framework的CannotGetJdbcConnectionException, 这是 Spring Framework的非检查型数据访问异常(DataAccessExceptions)的一种层次. 这个方法给你的信息比 SQLException给你的信息多, 并且确保跨数据库, 即使是不同的持久化技术的可移植性.

该方法同样可以独立于Spring事务管理工作(事务同步是可选的), 所以你可以使用它不管你是使用或者不使用 Spring的事务管理.

当然, 当你使用了Spring的JDBC支持, JPA支持或者Hibernate支持,你通常会喜欢不使用DataSourceUtils 或者其他的帮助类, 因为你会非常愉快的使用Spring直接基于相关API的抽象. 例如, 如果你使用Spring的 JdbcTemplate或者jdbc.object包来简化你使用JDBC的代码, 真正的连接都是发生在幕后的, 你不需要 亲自写代码.

11.4.3 事务发现数据源代理(TransactionAwareDataSourceProxy)

在非常底层存在TransactionAwareDataSourceProxy类. 这是一个针对DataSource的代理, 它包装目标的 数据源来添加Spring的事务管理支持. 在这方面, 他就和应用服务器提供的事务型的JNDI DataSource是相似的.

它应该是从来没必要或不需要使用的类, 除非存在代码必须要通过这种方式调用标准的JDBC的DataSource接口 的实现. 那样的话, 这个代码可以工作, 但同样使用Spring管理的事务. 推荐你用上面提到的更高层次的抽象来 写新的代码.

11.5 声明式事务管理

[Note]Note

大多数Spring Framework的用户选择声明式事务管理. 这种方式对应用代码的影响最小, 并且最符合一个非 侵入型轻量级容器的理想.

Spring Framework的声明式事务管理是建立在Spring的面向切面编程(aspect-oriented programming, AOP) 上的, 尽管如此, 作为Spring Framework发行包代码的一部分并且还可以在很多地方使用, AOP的概念却不需要 在这里使用代码的时候去理解.

Spring Framework的声明式事务管理在指定事务行为(或者缺少它)下降至单个代码层次的方面和EJB的CMT很像. 它能在必要的时候使得一个setRollbackOnly()调用能够在一个事务上下文中进行. 这两种事务管理不同的地方 在于:

  • 不像EJB的CMT试图使用JTA, Spring Framework的声明式事务管理工作在任何环境中. 它能通过简单的配置 文件设置就使用JTA 或者本地的事务管理如JDBC, JPA, Hibernate或者JDO.
  • 你可以将Spring Framework的声明式事务管理应用于任何的类, 而不是特定的类, 如EJBs.
  • Spring Framework提供了声明式的回滚规则, 这是 EJB所没有的. 包括编程式和声明式的回滚规则都是提供了的.
  • Spring Framework通过使用AOP允许你自定义事务的行为.例如, 你可以在事务回滚的中间插入自定义的行为. 你可以随着事务规则(You transactional advice)添加任意的规则. 使用EJB的CMT, 除了使用 setRollbackOnly(), 你不能影响容器的事务管理.
  • Spring Framework不支持高端应用服务器拥有的跨远程调用的事务上下文传播. 如果你需要这个功能, 我们 推荐你使用EJB. 然而, 在使用这个功能前请仔细考虑, 因为在通常情况下, 我们都不希望有跨远程调用的 事务.

回滚规则的概念是重要的: 他们让你可以指定哪些异常(或者Throwable)应该触发自动的回滚. 你指定这些通过在 配置文件中声明的方式, 不是在Java代码中. 所以, 虽然你仍然可以在TransactionStatus对象上调用 setRollbackOnly()来让当前事务回滚, 但是你大多数时候会指定一条规则让MyApplicationException必须 回滚. 这样做的显著优点是业务对象不会依赖于事务的底层组成. 例如, 他们通常不会引入Spring事务的API或者 其他Spring的API.

尽管EJB容器在遇到系统异常(system exception) (通常是运行时异常)的时候默认行为都是回滚事务, 但是 EJB的CMT在遇到应用异常(application exception) (他是一个不同于java.rmi.RemoteException 的检查型异常)的时候是不会自动回滚事务的. 虽然Spring在声明式事务管理的默认行为是按照EJB的约定(自动回滚 只在不检查型异常生效)来的, 但是自己定义行为还是很有用的.

11.5.1 理解Spring Framework的声明式事务实现

这是要告诉你简单的为你的类注释上@Transactional的注释, 为配置加上@EnableTransactionManagement 是不够充分的, 除非你理解了他们全部是如何工作的. 本章将向你讲解Spring Framework内部声明式事务管理的 组件在事务相关问题出现时的工作机制.

掌握Spring Framework声明式事务支持的关键是这个支持是通过AOP 代理起作用的, 以及事务声明是由元数据(metadata) (现在是XML配置或者基于注解的)驱动的. AOP和 事务型的元数据组合让步于使用AOP代理的TransactionInterceptorPlatformTransactionManager实现 来驱动方法级响应.

从概念上来讲, 在事务型代理上调用一个方法看起来像这样…

Figure 11.1. 

tx

11.5.2 声明式事务实现的例子

请参考下面的接口以及他们相关的实现. 这个例子使用FooBar类来表示占位符, 所以你可以专心于事务的 使用方法而不必关心特殊的域模型. 这个例子的目的是说明在DefaultFooService类的实现的每一个方法中 抛出UnsupportedOperationException异常的实例是很好的; 它允许你观察事务创建以及回滚来响应 UnsupportedOperationException异常的实例.

//我们想使之支持事务的服务层接口

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}
//上面接口的一个实现

package x.y.service;

public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        throw new UnsupportedOperationException();
    }

    public Foo getFoo(String fooName, String barName) {
        throw new UnsupportedOperationException();
    }

    public void insertFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

    public void updateFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

}

来让我们假设, FooService接口的前两个方法getFoo(String)getFoo(String, String)必须在只读 类型语义的事务上下文中执行, 并且其他的方法insertFoo(Foo)updateFoo(Foo)必须在可读可写类型 语义的事务上下文环境中执行. 下面的配置的详细解释将在接下来的段落中进行.

<!-- 来自文件 'context.xml' -->
<?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="fooService" class="x.y.service.DefaultFooService"/>

    <!-- 事务化配置(请看下面的<aop:advisor/>) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- 事务语义... -->
        <tx:attributes>
            <!-- 所有用'get'开头的方法都是只读的 -->
            <tx:method name="get*" read-only="true"/>
            <!-- 其他的方法使用默认的事务配置(看下面) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- 使得上面的事务配置对FooService接口的所有操作有效 -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>

    <!-- 不要忘了DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>

    <!-- 同样的, 也不要忘了PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 关于其他的<bean/>的定义 -->

</beans>

检查前面的配置. 你想让一个服务层对象, 就是fooService这个bean, 支持事务. 应用的关于事务语义的封装 是定义在<tx:advice/>的. 那<tx:advice/>的定义的意思就是"… 所有以'get'开头的方法都运行 在只读的事务语义中, 并且其他的所有方法都运行在默认的事务语义中". <tx:advice/>标签的 transaction-manager属性就是用来设置用来驱动事务的bean`PlatformTransactionManager`的名称, 在这里就是txManager这个bean.

[Tip]Tip

如果你打算填写在事务配置(<tx:advice/>)的transaction-manager属性的PlatformTransactionManager bean的名称是transactionManager, 你可以忽略掉. 如果你想写的PlatformTransactionManager`bean是 其他的名称, 你就必须要使用`transaction-manager属性来指明, 就像上面的例子.

<aop:config/>的定义确保了由txAdvice这个bean定义的事务配置在程序合适的切入点运行. 首先需要定义 一个切入点来匹配FooService( fooServiceOperation)这个接口定义的任何操作. 然后用一个顾问(advisor) 将切入点与txAdvice关联起来. 这样做的结果就是使用txAdvice定义的配置会在fooServiceOperation 上面工作起来.

在元素<aop:pointcut/>中使用的表达式是一个AspectJ的切入点表达式; 查看 Chapter 9, Aspect Oriented Programming with Spring了解更多关于Spring 切入点表达式的详细内容.

让整个服务层都是事务型的是一个通常的需求. 要这么做的最好方式就是简单的修改切入点表达式, 使之能够匹配 服务层所有的操作. 就像下面这样:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
[Note]Note

在这个例子中, 我们假设了你所有的服务层接口都是定义在x.y.service包中; 查看 Chapter 9, Aspect Oriented Programming with Spring了解更多.

现在我们已经分析了配置, 你可能就要问自己了, "好吧… 但是这些配置到底做了些什么呢?".

上面的配置将会在由fooService这个bean的定义创建的对象之上创建一个事务型的代理. 这个代理将会使用 事务配置践行配置, 所以当合适的方法在这个代理上被调用的时候, 一个事务是被开启、悬空、标记为只读 还是怎么样, 这得取决于这个方法的事务配置语义.请参看下面这个测试执行上面配置的例子:

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
        FooService fooService = (FooService) ctx.getBean("fooService");
        fooService.insertFoo (new Foo());
    }
}

上面的程序执行起来的输出看起来像这样. (为了看起来更直观, 已经将Log4J的输出和由DefaultFooService 类的insertFoo(..)方法抛出的UnsupportedOperationException堆栈进行了摘除处理.)

<!-- Spring容器正在启动... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean fooService with 0 common interceptors and 1 specific interceptors

<!-- DefaultFooService是真的被代理了 -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... insertFoo(..)方法现在是被代理调用的 -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- 事务配置切入在这里... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!-- DefaultFooService的insertFoo(..)抛出了一个异常... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- 然后事务就回滚了 (默认情况下, RuntimeException会导致回滚) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- 为了直观, AOP组件的堆栈已经移除了-->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

11.5.3 回滚一个声明式事务

前面的章节已经大致介绍了怎么给类指明事务配置, 这些类在你的应用里面通常是服务层的类. 这个章节将会描述 你怎么在一个简单的场景里面回滚事务.

让Spring Framework事务的基础构件知道事务需要进行回滚的推荐做法是在正在执行的代码的当前上下文中抛出 Exception. Spring Framework事务的基础构件将会在调用栈中出现未处理的Exception的时候将其全部 捕获, 然后会进行测定是否需要将事务进行回滚.

在默认配置中, Spring Framework的事务基础构件只会在运行期、未检查的异常时才会标记事务回滚;也就 是说, 当抛出的异常是RuntimeException或者其子类的实例时(Error也同样)默认都是标记为回滚. 事务的方法中抛出检查的异常时在默认情况下不会标记为回滚.

你可以自己配置哪些Exception的类型是需要标记为回滚的, 这包括了检查的异常. 下面的XML代码片段展示了 你需要怎样配置标记检查的、程序自定义的Exception为需要回滚异常.

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

如果你需要在某一些异常抛出的时候不进行回滚, 你一样可以配置不回滚规则. 下面的例子就告诉 Spring Framework的事务基础构件提交所进行的事务即使出现了未处理的InstrumentNotFoundException.

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

当Spring Framework的事务基础构件捕获了一条被多个参考配置确定是否需要回滚的异常时, 那一条最精确 的将生效.所以在下面的配置中, 除了InstrumentNotFoundException的所有异常都将被标记为回滚.

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    </tx:attributes>
</tx:advice>

你也可以以编程的方式标明一个需要回滚的地方. 尽管是很简单的, 但是也很具有侵入性, 并且将你的代码同 Spring Framework的事务基础构件紧耦合在了一起:

public void resolvePosition() {
    try {
        // 业务逻辑...
    } catch (NoProductInStockException ex) {
        // 编程式触发回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

强烈建议你在所有可能的情况下都使用声明式的方法让事务回滚. 编程式的事务回滚在你迫不得已的时候也是可行的, 但他的用例运行在在实现一个基于POJO的架构中.

11.5.4 为不同的bean配置不同的事务

考虑这样一个场景, 你在服务层有大量的对象, 并且你想对它们每一个都应用完全不同的事务配置. 你完成 这个事情是使用了不同的pointcutadvice-ref属性的值来定义了不同的<aop:advisor/>元素.

作为一个出发点, 首先假设你服务层所有的类都定义在根包x.y.service中. 为了让在这个包(或者他的子包) 中定义的所有以Service结尾的类的所有实例都具有默认事务配置, 你将会进行如下配置:

<?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">

    <aop:config>

        <aop:pointcut id="serviceOperation"
                expression="execution(* x.y.service..*Service.*(..))"/>

        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

    </aop:config>

    <!-- 这两个bean将支持事务... -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

    <!-- ... 而这两个bean将不支持 -->
    <bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->

    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- 省略其他如PlatformTransactionManager的事务基础构件的配置... -->

</beans>

下面的例子展示了怎样配置两个不一样的bean使用两个完全不同的事务配置.

<?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">

    <aop:config>

        <aop:pointcut id="defaultServiceOperation"
                expression="execution(* x.y.service.*Service.*(..))"/>

        <aop:pointcut id="noTxServiceOperation"
                expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

    </aop:config>

    <!-- 这个bean是事务型的(查看'defaultServiceOperation'切入点) -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- 这个bean也是事务型的, 但是它拥有完全不一样的事务配置 -->
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

    <tx:advice id="defaultTxAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <tx:advice id="noTxAdvice">
        <tx:attributes>
            <tx:method name="*" propagation="NEVER"/>
        </tx:attributes>
    </tx:advice>

    <!-- 省略其他如PlatformTransactionManager的事务基础构件的配置... -->

</beans>

11.5.5 <tx:advice/> 设置

本章总结整理可以使用<tx:advice/>标签指定的各种设置. <tx:advice/>标签默认的设置是:

  • 传播行为设置REQUIRED.
  • 隔离等级是DEFAULT.
  • 事务是可读可写.
  • 事务超时是使用系统底层组件的默认值, 在不支持超时的时候没有超时.
  • 任何的RuntimeException均触发回滚, 并且检查的Exception不会.

你可以修改默认的设置; <tx:advice/><tx:attributes/>标签所需要的<tx:method/>标签的属性都 整理在下面了:

Table 11.1. <tx:method/>设置

属性是否必须默认值描述

name

 

事务属性所关联的方法名称(可能不唯一). 通配符(*)可以用于表示一组相同的方法; 例如, get*, handle*, on*Event, 等等.

propagation

不是

REQUIRED

事务传播行为.

isolation

不是

DEFAULT

事务隔离等级.

timeout

不是

-1

事务超时的值(以秒为单位).

read-only

不是

false

事务是不是只读的?

rollback-for

不是

 

会触发回滚的Exception(可能不唯一); 使用逗号分隔. 例如, com.foo.MyBusinessException,ServletException.

no-rollback-for

不是

 

不会触发回滚的Exception(可能不唯一); 使用逗号分隔. 例如, com.foo.MyBusinessException,ServletException.


11.5.6 @Transactional 的使用

作为使用基于XML配置声明式事务配置方法的补充, 你可以使用一种基于注解的方法. 直接在Java代码中声明事务 语义声明使得声明更加靠近生效的代码. 这不存在过度危险的耦合, 因为不管怎么说开发代码的就意味着这样 被事务化地使用.

@Transactional注解所提供的易用性将使用后面文本中的例子进行说明. 参考下面声明的类:

// 我们想要支持事务的服务类
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

当在Spring IoC容器中定义上面的POJO时, 这个bean的实例就仅仅需要在XML配置添加行就可以添加 事务了:

<!-- 来自文件context.xml -->
<?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="fooService" class="x.y.service.DefaultFooService"/>

    <!-- 使使用注解配置的事务行为生效 -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- 仍然需要一个PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- (这个需要的对象是在其他地方定义的) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 其他<bean/>的定义 -->

</beans>
[Tip]Tip

如果你想在<tx:annotation-driven/>标签里面的transaction-manager属性值写的 PlatformTransactionManager对象的bean名字是transactionManager的话可以忽略. 如果你需要 依赖注入的PlatformTransactionManager`bean的名字是另外的, 你需要想前面例子中的那样使用 `transaction-manager属性来指定.

[Note]Note

如果你是使用基于Java的配置的话那么@EnableTransactionManagement注解提供了等效的支持. 只需要简单的 在类上添加@Configuration.在API文档中查看详细信息.

你可以把@Transactional注解添加在接口定义、接口中的方法定义、类定义、或者一个类中public方法的 前面. 然而, 仅仅有@Transactional注解的存在还不足以使事务的行为生效. @Transactional注解仅仅是 一个用来让某些运行期@Transactional-发现的基础构件来发现的元数据, 并且这些发现还会使用这个元数据 来配置bean的事务行为. 在前面的例子中, 元素`<tx:annotation-driven/>`开启了事务行为.

[Tip]Tip

Spring建议你只为具体类(以及具体类的方法)添加@Transactional注解, 而不要给接口添加注解. 你当然 也可以给接口(或者接口中的方法)添加注解, 但是这只会在你期望的使用的代理时基于接口的时候工作. Java中的 注解不会从接口继承的事实意味着如果你是使用的基于类的代理( proxy-target-class="true")或者基于 编织的方面( mode="aspectj"), 那么关于事务的设置不会被代理或者编织的基础设施注册, 并且对象就不会 被事务型的代理包装, 而这当然是不好的.

[Note]Note

在代理模式下(默认值), 只有来自外部方法的调用才会被代理拦截. 这意味着自我调用, 在效果上是, 目标对象的 一个方法调用了目标对象的另一个方法, 不会导致产生运行期的事务, 即使被调用的方法被@Transactional 标记了.

如果你想要自我调用也同样被事务包装的话, 参考AspectJ模式的使用(查看下面表格中的模式属性). 在这种情况 下, 首先将不再会有代理; 取而代之, 目标类将会被编织(也就是说, 它的字节码会被修改)来使得 @Transactional成为任何方法在运行期的行为.

Table 11.2. 基于注解的事务设置

XML属性注解属性默认值描述

transaction-manager

N/A (查看TransactionManagementConfigurer 的API文档)

transactionManager

要使用的事务管理的名字. 只有在事务管理的名字不是transactionManager的时候是必须的, 就像上面的 例子一样.

mode

mode

proxy

默认值"proxy"使得注解了的bean使用Spring的AOP框架来代理(依照代理的语义, 就像上面讨论的, 只有在 通过代理的方法调用时生效). 候选的"aspectj"模式使用了Spring的AspectJ的事务方面来编织了的类来 替换编织, 修改目标类的字节码来对任何方法调用都会应用. AspectJ编织需要spring-aspects.jar存在于 classpath中如果加载时编织(或者编译时编织)开启了.(查看the section called “Spring configuration”了解关于如何设置 加载时编织的详细信息.)

proxy-target-class

proxyTargetClass

false

只在代理模式生效. 控制使用@Transactional注解的类将会创建什么类型的事务型代理. 如果proxy-target-class 属性设置为true, 那么将会创建基于类的代理. 如果proxy-target-class设置为false或者这个属性 被忽略了, 那么将会创建标准的JDK的基于接口的代理. (查看Section 9.6, “Proxying mechanisms”了解不同代理类型的详细 信息.)

order

order

Ordered.LOWEST_PRECEDENCE

定义应用在添加了@Transactional注解的bean上的事务通知的顺序. (更多关于AOP通知的顺序的信息, 请 查看the section called “Advice ordering”.) 不指定顺序意味着由AOP底层系统决定通知的顺序.


[Note]Note

proxy-target-class属性控制着使用@Transactional注解的类将会创建什么类型的事务型代理. 如果 proxy-target-class被设置为true, 就会创建基于类的代理. 如果proxy-target-class设置为false 或者这个属性被忽略了, 将会创建标准的JDK的基于接口的代理代理. (查看Section 9.6, “Proxying mechanisms”了解关于不同 代理类型的讨论.)

[Note]Note

@EnableTransactionManagement<tx:annotation-driven/>只会查找在同一个应用上下文中定义的 bean的@Transactional注解. 这意味着, 如果你在一个DispatcherServletWebApplicationContext 中添加了注解驱动的配置, 那么只会在控制器中查找`@Transactional`bean, 而不会查找服务层. 查看 Section 16.2, “The DispatcherServlet”了解更多.

在决定方法的事务设置时, 最精确的配置优先. 在下面的例子中, DefaultFooService是一个在类级别使用只读 事务设置的类, 但是在同一个类的updateFoo(Foo)方法上的@Transactional注解优先于在类级别的事务设置.

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // do something
    }

    // 该方法的设置更优先
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

@Transactional 设置

@Transactional注解是一个用来定义一个接口、类或者方法必须具备事务化语义的元数据; 例如, "在调用 该方法时挂起所有已经存在的事务,开始一个新的只读事务". 下面是@Transactional注解的默认设置:

  • 传播设置是PROPAGATION_REQUIRED.
  • 隔离等级是ISOLATION_DEFAULT.
  • 事务是可读可写的.
  • 事务超时是使用底层事务系统的默认值, 或者在不支持时没有.
  • 任何的RuntimeException触发回滚, 并且所有的检查的Exception不触发.

这些默认设置都是可以修改的; @Transactional注解的各种属性都整理在下面的表格中了:

Table 11.3. @Transactional

属性类型描述

value

String

指定事务管理器使用的可选限定符.

propagation

enum: Propagation

指定传播属性设置.

isolation

enum: Isolation

指定隔离等级.

readOnly

boolean

设置事务是可读可写还是只读

timeout

int (用秒作为粒度)

事务超时.

rollbackFor

Class对象数组, 必须是继承自Throwable.

指定 必定会触发回滚的类的数组.

rollbackForClassName

类名的数组. 类必须是继承自Throwable.

指定 必定会触发回滚的异常类的数组.

noRollbackFor

Class对象的数组, 这些类必须是继承自Throwable.

指定 必定不会触发回滚的类的数组.

noRollbackForClassName

类的字符串数组, 这些类都必须继承自Throwable.

指定 必定不会触发回滚的异常类的数组.


当前你还不可能明确拥有通过名称对事务的控制力, 如果可以的话(例如, WebLogic的事务记录器), 这个名称 指的是在事务记录器中显示的事务名称, 也是在日志输出中. 对于声明式事务, 事物的名称总是类的完整限定名+ "."+事务化通知配置的方法名. 例如, 如果BusinessService类的handlePayment(..)方法打开了一个 事务, 那么名字就会是: com.foo.BusinessService.handlePayment.

@Transactional 使用多个事务管理器

大多数的Spring应用都只需要一个事务管理器, 但也存在你需要在一个单一应用中使用多个不同的事务管理器的情况. @Transactional注解的value属性可以用来指定要使用的不同的PlatformTransactionManager. 这可以是 bean的名称或者是事务管理器bean的修饰值. 例如, 要使用修饰符号, 下面的Java代码

public class TransactionalService {

    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }
}

可以和下面在应用上下文声明的事务管理器的bean进行绑定.

<tx:annotation-driven/>

    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="order"/>
    </bean>

    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="account"/>
    </bean>

在这种情况下, TransactionalService中的两个方法将会分别运行在独立的事务管理器中, 通过"order"和 "account"的修饰符来区分. 默认的<tx:annotation-driven>的目标bean的名称transactionManager 仍然将会在指定的PlatformTransactionManager的bean的修饰符号没有被找到的时候使用.

自定义快捷注解

如果你发现你反复在许多不同的方法上使用@Transactional注解相同的属性, 那么 Spring的基础注解支持将允许你针对你的特定使用场景自定义快捷注解. 例如,定义如下的注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

将允许我们将前面章节的例子改写为

public class TransactionalService {

    @OrderTx
    public void setSomething(String name) { ... }

    @AccountTx
    public void doSomething() { ... }
}

这里我们使用了定义事务管理器的语法,但是我们完全可以包含进传播性行为、回滚规则、超时等等.

11.5.7 事务传播性

本部分讲解一些关于Spring事务传播性行为的语义. 请记住本部分并不是介绍事务的传播性本身; 尽管它比Spring 中的事务传播性更加详细.

在Spring的受管事务中, 存在物理逻辑事务的差别, 以及还有传播性的设置是怎样在这种差别上生效的.

需要 Required

Figure 11.2. 

tx prop required

PROPAGATION_REQUIRED

当传播属性设置为PROPAGATION_REQUIRED时, 将会为设置应用到的每一个方法创建一个逻辑上的事务 作用域. 这每一个单独的逻辑事务作用域可以单独的确定回滚状态, 在逻辑上独立于事务范围的外部事务范围. 当然, 考虑到标准的PROPAGATION_REQUIRED的行为, 所有的这些作用域都将会映射到相同的物理事务上. 因此, 在内部事务作用域中作的事务回滚标记确实会影响到外部事物实际上提交的可能性(这和你所期待的一样).

然而, 在内部事务作用域中标记了回滚, 外部事物决定它自己不回滚的情况下, 这样的回滚(由内部事务作用域 静默触发)就不是期待的了. 一个对应的UnexpectedRollbackException将会在在那里抛出. 这是一个异常 行为, 所以事务的调用者将不可能会在事务其实没有提交的时候被误导为假设提交了. 所以对于内部事务作用域 (在外部调用者没有发觉时)静默的标记了回滚的情况下, 外部调用者调用了提交. 那么外部调用者需要收到一个 UnexpectedRollbackException来清楚的知道需要用一个回滚来取而代之(提交).

需要新的 RequiresNew

Figure 11.3. 

tx prop requires new

PROPAGATION_REQUIRES_NEW

相比较于PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW对每一个受影响的事务作用域都使用完全 独立的事务. 这样, 物理上的事务就不同了并且可以独立的提交或者回滚, 外部事物不会影响到内部事务的回滚 状态.

嵌套 Nested

PROPAGATION_NESTED对多个可以回滚到的保存点使用了一个单独的底层事务. 这种局部化的回滚允许一个 内部事务触发一个针对它的作用域的回滚, 尽管一些操作已经回滚了, 但外部事物还是可以继续物理上的事务. 这个设置通常都和JDBC的保存点对应, 所以只会在JDBC的资源的事务上有作用. 请查看Spring的 DataSourceTransactionManager.

11.5.8 通知事务操作

假设你想要同时执行事务型的一些基本的分析通知. 你怎样在<tx:annotation-driven/>的上下文 中体现?

当你执行updateFoo(Foo)方法时, 你期望看到下面的动作:

  • 配置了分析通知的切面启动.
  • 事务通知执行.
  • 被添加了通知的对象的方法执行.
  • 提交事务.
  • 分析切面报告整个事务方法执行的准确时间.
[Note]Note

本章节不会详细阐述AOP(除了适用于事务). 查看Chapter 9, Aspect Oriented Programming with Spring了解下面有关AOP配置的详细信息以及AOP的其他信息.

这里是上面讨论的简单分析切面的代码. 通知的排序由Ordered接口控制. 完整的通知排序的信息请查看 the section called “Advice ordering”.

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;

public class SimpleProfiler implements Ordered {

    private int order;

    // 允许我们对通知排序
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    // 这个方法关于通知
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}
<?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="fooService" class="x.y.service.DefaultFooService"/>

    <!-- 这是切面 -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- 在事务通知之前执行(更低的排序) -->
        <property name="order" __value="1"__/>
    </bean>

    <tx:annotation-driven transaction-manager="txManager" __order="200"__/>

    <aop:config>
            <!-- 这个通知将会在事务通知执行时执行 -->
            <aop:aspect id="profilingAspect" ref="profiler">
                <aop:pointcut id="serviceMethodWithReturnValue"
                        expression="execution(!void x.y..*Service.*(..))"/>
                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
            </aop:aspect>
    </aop:config>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

上面配置的结果就是对一个叫做fooService的bean进行分析并且也在合适的顺序应用了事务切面. 你也 可以使用相似的方法配置任何数字的补充切面.

下面的例子和上面的有一样的作用, 但是使用了纯粹的XML声明方式.

<?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="fooService" class="x.y.service.DefaultFooService"/>

    <!-- 分析通知 -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- 在事务通知之前执行(更低的排序数字) -->
        __<property name="order" value="1__"/>
    </bean>

    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
        <!-- 将在分析的通知执行之后执行(注意order属性) -->

        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" __order="2__"/>
        <!-- order的值比分析切面的高 -->

        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
                    expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>

    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- 其他的<bean/>的定义, 如DataSource和PlatformTransactionManager -->

</beans>

上面配置的结果就是分析一个叫做fooService的bean并且在他之上应用了指定的事务切面. 如果你想让 你的分析通知在进入的时候在事务通知之后执行, 并且在出去的时候在事务通知之前执行, 你只需要 简单的交换分析切面bean的order属性的值, 使得它比事务通知的排序值更高就行.

你配置其他切面也使用类似的方式.

11.5.9 与AspectJ一起使用@Transactional

使用Spring Framework的@Transactional来支持Spring容器外由AspectJ切面所定义的事务也是可以的. 要这样做, 你首先需要在你的类(或者针对可选的类的方法)上使用@Transactional注解进行注解, 然后使用 spring-aspects.jar文件里面定义的 org.springframework.transaction.aspectj.AnnotationTransactionAspect对应用进行链接(编织). 指定的切面也必须使用事务管理器进行配置. 你当然可以使用Spring Framework的IoC容器来负责进行切面的 依赖注入.配置事务管理器切面的最简单方法是使用<tx:annotation-driven/>元素并且对属性mode使用 Section 11.5.6, “@Transactional 的使用”里面描述的aspectj. 因为我们这里关注的是程序运行在 Spring容器外部, 所以我们将向你展示编程式的方法.

// 构造一个适当的事务管理器
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// 配置AnnotationTransactionAspect去使用它; 这个操作必须在执行事务方法之前进行
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
[Note]Note

当使用切面时, 你必须在实现类(以及/或者说这些类的方法)上进行注解, 不要在实现类的接口(如果有)上 注解. AspectJ遵循Java的规则在接口上的注解不会继承.

在类上的@Transactional注解定义了执行类中所有方法的默认事务行为.

在类中方法上的@Transactional注解覆盖了由类上的注解(存在的话)定义的默认事务行为. 任何方法均可以 被注解, 不论其访问可见性如何.

为了使用AnnotationTransactionAspect编织你的应用你必须连同AspectJ进行构建(查看 AspectJ开发指南)或者使用 加载时编织. 查看Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”了解关于使用AspectJ进行加载时编织的讨论.

11.6 编程式事务管理

Spring Framework提供了两种方式的编程式事务管理:

  • 使用TransactionTemplate.
  • 直接使用PlatformTransactionManager的一个实现.

Spring一般都推荐使用TransactionTemplate来进行编程式事务管理. 第二种方式有点类似于使用JTA的 UserTransaction接口, 尽管异常处理没有那么复杂化了.

11.6.1 使用TransactionTemplate

TransactionTemplate采用了像JdbcTemplate等其他Spring的templates的一样的方式. 它才用了 回调的方式来使得应用的代码脱离需要的样本采集和食物资源释放, 并且在代码中表现为意图驱动, 这样代码的 编写就可以完全只关注于开发人员想要做的部分.

[Note]Note

就像你将在下面的例子中看到的那样, 使用TransactionTemplate会把你和Spring的事务基础设施和API绑定 在一起. 用与不用编程式事务管理是不是符合你们开发的需求取决于你自己.

应用代码必须在事务上下文中执行, 并且必须显示地使用TransactionTemplate, 看起来就像下面这样. 你, 作为一名开发者, 编写一个TransactionCallback的实现类(通常表现为一个匿名内部类)来包含需要在事务 上下文中执行的代码. 然后你需要将你自定义的TransactionCallback的实例传递给在TransactionTemplate 中暴露出来的execute(..)方法.

public class SimpleService implements Service {

    // 在这个实例而的所有方法中共享的TransactionTemplate单例
    private final TransactionTemplate transactionTemplate;

    // 使用构造器注入PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // 本方法的代码在事务上下文中执行
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

如果没有返回值的话, 使用方便的TransactionCallbackWithoutResult的匿名类就像下面这样:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});

在回调方法中可以通过调用TransactionStatus对象提供的setRollbackOnly()方法来回滚事务:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            updateOperation1();
            updateOperation2();
        } catch (SomeBusinessExeption ex) {
            status.setRollbackOnly();
        }
    }
});

指定事务设置

你可以指定事务设置, 如传播类型、隔离等级、超时, 都可以通过TransactionTemplate设置, 不管是编程式 还是配置文件. TransactionTemplate的实例默认拥有 默认事务配置. 下面的代码展示了对 TransactionTemplate的事务配置的定制方法:

public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
        this.transactionTemplate = new TransactionTemplate(transactionManager);

        // 如果需要, 可以将需要的配置明确的设置在这里
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        this.transactionTemplate.setTimeout(30); // 30 秒
        // 还可以有更多...
    }
}

下面的代码使用Spring XML配置文件使用自定义的事务设置定义了一个TransactionTemplate. sharedTransactionTemplate可以被注入到很多需要的服务里面.

<bean id="sharedTransactionTemplate"
        class="org.springframework.transaction.support.TransactionTemplate">
    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
    <property name="timeout" value="30"/>
</bean>"

最后, TransactionTemplate类的实例时线程安全的, 在一个实例里面不会维护任何会话的状态. 但是 TransactionTemplate的实例却维护配置的状态, 所以尽管许多的类可以共享一个 TransactionTemplate的单例, 但如果某一个类需要TransactionTemplate拥有不一样的设置(例如, 不一样的隔离等级), 那么你需要创建两个TransactionTemplate的不同实例.

11.6.2 PlatformTransactionManager的使用

你也可以直接使用org.springframework.transaction.PlatformTransactionManager来管理你的事务. 只需简单的将你所使用的PlatformTransactionManager的实现类通过一个bean引用传递给你的bean. 然后, 你就可以使用TransactionDefinitionTransactionStatus对象来新建、回滚、提交事务了.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 显示指定事务名称是只能通过编程式才能做到的
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // 在这里执行你的业务逻辑
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

11.7 在编程式和声明式事务管理中选择

编程式事务管理只在你只有很小数量的事务操作的时候是好主意. 例如, 如果你有一个web应用只有确定的更新 操作需要事务, 你就有可能不想通过Spring或者其他技术来建立事务代理. 在这种情况下, 使用 TransactionTemplate可能是一个好的方法. 可以明确的设置事务的名字也是只有编程式事务管理才能 做的一些事情之一.

另一方面, 如果你有大量的事务操作, 那么声明式事务管通常是值得的. 它让事务管理和业务逻辑分开, 并且也 不难配置. 当使用Spring Framework时, 不想EJB CMT, 声明式事务管理的配置大大的减少了.

11.8 应用服务器的特异性集成

Spring的事务抽象通常都是和应用服务器无关的. 补充一下, Spring的JtaTransactionManager类, 能够 可选地执行一个JNDI发现来自动地定位由于应用服务器改变的JTA的UserTransactionTransactionManager 的最新对象. 通过访问JTA的TransactionManager允许操作事务语义, 以一种特殊的方式来支持事务悬停. 查看JtaTransactionManager的API来了解详情.

Spring的JtaTransactionManager是运行在Java EE应用服务器上面的标准选择, 并且也是已知的能够在 所有的通用服务器上很好工作的. 高级功能, 如事务悬浮, 能够在许多服务器上很好工作————包括GlassFish, JBoss以及Geronimo————不需要额外的特殊配置. 然而, 为了完整的支持事务悬浮和更多的高级特性的集成, Spring为WebLogic Server和WebSphere提供了特殊的适配器. 关于这些适配器将在下面的章节中讨论.

在标准场景下, 包括WebLogic Server和WebSphere, 考虑使用方便的<tx:jta-transaction-manager/> 配置元素. 当配置之后, 这个元素就会自动探测底层服务器并且选择针对平台可用的最佳的事务管理器. 这也 意味着你不需要单独明确配置针对服务器的适配器类(例如下面将要讨论的); 对应的, 它们是由标准的 JtaTransactionManager使用默认的值自动选择的.

11.8.1 IBM WebSphere

在WebSphere 6.1.0.9及更高版本中, 推荐使用的Spring JTA事务管理器是 WebSphereUowTransactionManager. 这个特殊的适配器使用了IBM的UOWManager API, 这个实在WebSphere 应用服务器的6.0.2.19及更高版本和6.1.0.9及更高版本中可用的. 通过这个适配器, Spring提供的事务悬浮 (如PROPAGATION_REQUIRES_NEW开启的挂起/恢复)就是由IBM官方支持的了!

11.8.2 Oracle WebLogic Server

在WebLogic Server 9.0或更高版本中, 你通常应该使用WebLogicJtaTransactionManager来代替备用的 JtaTransactionManager类. 这个特殊的有WebLogic声明的正常的JtaTransactionManager的子类支持 全部的在WebLogic管理的环境中的Spring的事务定义, 超越了标准的JTA语义: 特性包括事务名称, 每个事务的 隔离等级, 以及在所有场景中的事务的正常恢复.

11.9 通用问题的解决

11.9.1 特殊DataSource使用错误的事务管理器

使用正确的 PlatformTransactionManager实现取决于你对事务技术的选择和需求.使用合适了, Spring Framework仅仅是提供了简单又编写的抽象. 如果你使用全局的事务, 你必须在所有的事务操作中 使用org.springframework.transaction.jta.JtaTransactionManager类(或者是它 应用服务器定义的子类).否则事务基础设施会尝试在 如容器的DataSource等资源上执行本地事务. 这些本地事务不会有效果, 并且好的应用服务器会把他们当做 错误.

11.10 更多资源

关于Spring Framework的事务支持的更多信息:

  • Spring 的分布式事务, 使用和不使用XA是一个JavaWorld 的演示, 通过7个在Spring应用中的分布式事务, Spring的David Syer引导你, 3个使用了XA, 4个没有使用.
  • Java事务设计策略是一本来自 InfoQ的书籍, 使用了很好的节奏来介绍介绍了Java中的事务. 它也包含了怎样配置和使用Spring Framework和EJB3事务的对比例子.