Spring Framework之Transaction Management

前言

原文链接,本文主要翻译官方文档,同时可能也会对笔者认为不够清楚的地方加上说明、示例、链接等。

Spring Framework事务管理介绍

全面事务支持是使用Spring Framework最令人信服的理由之一。Spring Framework为管理事务提供了一致性的抽象,具有以下优点:

  • 跨不同事务API的一致性编程模型,例如Java事务API(JTA)、JDBC、Hibernate、Java持久化API(JPA)、和Java数据对象(JDO)。
  • 支持声明式事务管理
  • 用于编程事务管理的API比复杂的事务API(如JTA)更简单。
  • 与Spring的数据库访问抽象的完美集成。

以下部分描述了Spring Framework的事务增值和技术。本章还讨论了最佳实践、应用服务器集成和常见问题的解决方案。)

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

传统上,Java EE开发者对事务管理有两个选择:全局或者本地事务,两者都有很大的局限性。在接下来两节中回顾全局和本地事务管理,紧接着讨论Spring Framework的事务管理如何解决全局和本地事务模型的局限性。

全局事务

全局事务是你能够使用多数据源,通常是关系数据库和消息队列。应用服务管理全局事务通过JTA。这是个使用其他很麻烦的API(部分是由于它的异常模型)。此外,JTA UserTransaction通常需要来源于JNDI(java Naming and Directory Interface,java命名和目录接口),意味着你也需要使用JNDI为了使用JTA。显然使用全局事务将限制应用代码的一些潜在的重用,因为JTA通常仅在application server环境中可用(就是本服务中,不能对多服务进行事务控制(如seata))。

之前,使用全局事务的首选方式是通过EJB CMT(Container Managed Transaction):CMT是声明式管理的一种形式(区别于程序化事务管理),EJB CMT取消了事务相关的JNDI查找的需要,尽管使用EJB本身当然需要使用JNDI。它消除了大部分但是不是全部需要编写Java代码来控制事务。CMT明显的缺点是和JTA和application server相关联。此外,它需要选择EJB中或至少在事务性EJB外观之后实现业务逻辑才可用。一般而言EJB的负面影响如此之大,以至于这不是一个吸引人的提议,尤其是在面对声明式事务管理的引人注目的替代方案时。

本地事务

本地事务是特定于资源的,例如与JDBC连接相关联的事务。本地事务也许更容易去使用,但有更大缺点:它们不能垮在多个事务资源使用。例如。代码使用JDBC连接管理的代码不能使用在全局JTA事务中。因为application server不处理事务管理,它不可能帮助确定横跨多个事务源的正确性。(它是值得注意的是大部分应用使用都是单事务资源)另一个去缺点是本地事务是对编程模型具有侵入性的。

Spring Framework的一致性编程模型

spring解决了全局事务和本地事务的缺点。它使用应用程序开发人员能够在任何环境中使用一个一致性编程模型。你编写一次代码,它可以在不同的环境中不同的事务策略收益。Spring Framework提供声明式和编程式事务管理。大部分用户更加喜欢声明式事务管理,这在大多数情况下是推荐的。

通过编程式事务管理,开发者使用Spring Frame事务抽象,它可以在任何事务管理基础架构上。使用更受欢迎的声明模式开发者通常写很少或者不写代码去使用事务管理,因此不依赖Spring Framework事务API和其他一些事务API。

理解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;
}

这是主要的服务提供者接口(SPI),尽管它可以在你的应用代码中以编程的方式被使用。因为PlatformTransactionManager是一个接口,它可以根据需要轻松模拟或存根。它不依赖于像JNDI之类的查找策略。PlatformTransactionManager的实现是被定义像任何一些其他对象在Spring Framework IOC容器中。即使你在使用JTA时,仅此好处就使Spring Framework事务成为有价值的抽象。事务代码可以比直接使用JTA更加容易进行测试。

再次和Spring的哲学保持一致,unchecked TransactionException是可以被抛出由任何PlatformTransactionManager接口的方法。事务基本功能失败几乎总是致命的。在极少情况下,应用代码实际上可以从一个事务的失败中恢复,应用开发者仍然可以选择捕获和处理TransactionException。重点是开发者没有强制这么做。

getTransaction(…)方法根据TransactionDefinition参数返回一个TransactionStatus对象。被返回的TransactionStatus可能代表一个新的事务或者代表一个存在的事务如果一个正在匹配的事务存在于调用栈中。这后面一种情况的含义是,与java EE事务中的上下文一样,一个TransactionStatus是被收集在一个执行线程中。

TransactionDefinition接口指定:

  • Isolation(隔离):此事务和其他事务的工作隔离的程度。例如,可以读到其他事务未提交的写?
  • Propagation(传播):通常,在一个事务范围内全部代码是在该事务中运行。但是,当一个事务上下文已经存在,你可以被执行的事务方法选择指定行为。例如,代码可以持续运行在当前事务中;或者存在的事务可以暂停,一个新的事务被创建。Spring提供所有类似于EJB CMT。要了解Spring中事务传播的语义,请参阅第 16.5.7 节,“事务传播”
  • Timeout:在超时之前事务可以运行多久,超时之后回被底层事务框架自动回滚。
  • Read-only状态:当你只读不修改数据的时候你可以使用Read-only事务。在某些情况下,只读事务可能是一种有用的优化,例如当你使用Hibernate时。

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 bean定义将引用DataSource定义,如下:

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

如果你在一个Java EE容器中使用JTA,然后你可以通过JNDI使用一个DateSource容器,并结合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,一些具体来源,因为它使用容器全局事务管理。

你可以轻松的使用Hibernate本地事务,就如下面的例子。在这种情况下,你需要定义一个Hibernate LocalSessionFactoryBean,在你的代码中将使用包含Hibernate Session实例。

DataSourcebean定义将是和定义本地JDBC类似的,前面已经展示,因此是不被展示在下面的例子中。

在例子中txManager bean属于HibernateTransactionManager类型。和DateSourceTransactionManager一样需要一个DataSource引用,HibernateTransactionManager需要引用SessionFactory。

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.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.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

如果你使用Hibernate和Java EE容器管理JTA事务,你应该简单使用相同的JtaTransactionManager就像之前的JTA的JDBC例子。

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

资源和事务的同步

你现在应该清楚如何创建不同的事务管理和它们是如何链接需要同步事务的关联资源(例如DataSourceTransactionManager同步关联一个JDBC DataSource,HibernateTransactionManager关联一个Hibernate SessionFactory等等)。这节描述应该使用要一个持久性AP例如JDBC、Hibernate或者JDO,确保这些资源是被创建、复用和适当地清理。本节还讨论了如何通过相关的PlatformTransactionManager触发(可选)事务同步

高级同步方法

首选方法是使用Spring基于持久化集成APIs的高级模版或者使用带有transaction-aware factory beans或者代理管理本地ORM APIs。这些transaction-aware解决方案在内部处理resource创建、重用、清理、可选事务资源同步和异常映射。因此用户数据访问不必处理这些任务,而可以专注于集中非模版的持久化逻辑代码。一般情况下,你可以使用本地ORM API或者通过使用JdbcTemplate使用一个JDBC访问模版方法。这些处理是被详细介绍在本参考文档的随后章节。

低级同步方法

诸如DataSourceUtils(对于JDBC),EntityManagerFactoryUtils(对于JPA)、SessionFactoryUtils(对于Hibernate)、PersistenceManagerFactoryUtils(对于JDO)等存在低级别的类。当你想要使用应用代码直接处理本地持久化APIs的resource类,你使用这些类来确保获得合适的Spring Framework-managed实例,事务(可选)同步,过程中发生的异常时正确的映射到一致性API中。

例如,在使用JDBC的情况下,替换传统的在DataSource上调用getConnection()方法的JDBC方法,而是使用Spring的org.springframework.jdbc.datasource.DataSourceUtils如下:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果一个存在中的事务存在一个与之同步的连接,则返回该实例。否则方法调用触发创建新的连接,该连接(可选)同步到任何现有事务,并可供该事务中的后续重用。如前所述,一些SQL Exception是包含在Spring Framework中的CannotGetJdbcConnectionException,是Spring Framework未经检查的DataAccessExceptiion之一。这种方法比SQLException中获得更多的信息,并确保方便的跨不同数据库甚至跨不同持久性技术。

这种方法也可以在没有Spring transation management(事务同步是可选的)生效,因此无论是否使用Spring transaction management你都可以使用它。

当然,一旦你使用了Spring的JDBC支持、JPA支持或者Hibernate支持,你通常不使用DataSourceUtils或其他帮助类,因为通过Spring 抽象比直接使用相关API会更快乐。如果你使用Spring JdbcTemplate(内部使用DataSourceUtils)或jdbc.object包去简化你使用JDBC,正确的连接发生在幕后,你无需编写任何特殊代码。

TransactionAwareDatasourceProxy

在最底层存在TransactionAwareDataSourceProxy类。这是一个对目标DataSource的代理,包装目标DataSource并添加Spring-managed transactions的awareness。在这个方面,它是类似于一个由Java EE服务器提供的事务性JNDI DataSource。

你应该几乎不需要使用此类,除非必须调用现有代码并传递一个标准的JDBC DataSource接口实现。在这种情况下,这个代码可能是可用的,但参与Spring managed transaction。它是更好使用上面提到的更高级别抽象方法去写你的新代码。

声明式事务管理

大多数Spring Framework使用者声明事务管理。这个选择对应用代码有最小的入侵,因此最符合非入侵轻量容器观念。

Spring Framework的声明式管理是通过Spring aspect-oriented programming(AOP)成为可能,尽管由于事务切面代码随着Spring Framework发布来到并且可以以样板的方式使用,AOP概念通常有效使用不需要必须被理解。

Spring Framework的事务管理是类似于EJB CMT,因为你可以指定transaction behavior(或缺少它)指定到单个方法级别。如果有必要可以在上下文中调用setRoolbackOnly()。两种事务管理的不同如下:

  • 与绑定到JTA的EJB CMT不同,Spring Framework的声明式事务工作的任何环境中。它可以通过简单调整配置文件和JTA transaction或者使用JDBC、JPA、Hibernate或者JDO的本地事务一起工作。
  • 你可以在任何类中应用Spring Framework声明式事务管理,不像EJB仅在一些特殊的类中。
  • Spring Framework通过声明式回滚规则,没有EJB的特性。回滚是支持编程式或者声明式的。
  • spring Framework使用能够定制事务的行为,由使用AOP。例如你可以在事务回滚的情况下插入定制行为。你可以添加任何advice以及事务的advice。在使用EJB CMT时你除了setRollbackOnly()之外不能影响容器的事务管理。
  • Spring Framework不支持跨远程调用事务上下文传播,高端应用服务也是如此。如果你需要这个功能,我们建议你使用EJB。然而,在使用此类功能之前需要仔细考虑,因为通常情况下,人们不希望事务跨远程调用。

回滚概念很重要的:他们确保你去指定哪一个异常(和throwables)应该造成自动回滚。你应该在配置中指定这个声明而不是在Java代码中。所以,虽然你仍然可以在TransactionStatus对象上调用setRollbackOnly()去回滚当前事务,大部分情况下你可以指定一个规则:MyApplicationException必须总是回滚。这个选项的明显优势是业务对象不依赖于事务底层。例如,他们通常不需要import Spring transction APIs或者其他Spring APIs。

虽然EJB容器默认行为是在一个system exception(通常是运行时异常)自动的回滚这事务,但是EJB CMT不在一个application exception(即除java.rmi.RemoteException之外的检查异常)自动回滚。当对于声明式事务管理的Spring默认行为遵循EJB约定(仅在unchecked异常中自动回滚),但定制行为通常是有用的。

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

它是不够的仅告诉你简单的去使用注解@Transactional去注解你的类、在你的configuraction上添加@EnabeTransactionManagement,然后期望你去理解它是如何工作的。本节解释在和事务相关问题时Spring Framework声明式事务底层是如何工作的。

关于Spring Framework的声明式事务,需要掌握的最重要的概念是这种通过AOP代理是支持的,并且事务的advice是被驱动由元数据(当前的XML-或者annotation-based)。AOP和事务的元数据组合产生了一个AOP代理(由TransactionInterceptor和一个适合的PlatformTransactionManager实现去驱动事务around method)。

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

在这里插入图片描述

声明式事务实现示例

考虑到以下接口和它的伴随实现。这个例子使用Foo和Bar类作为占位符,以便你可以集中精力在事务的使用而不是在特定的领域模型。对于例子的目的而言,DefaultFooService类在每一个方法体的实现中抛出UnsupportOperationException示例是很好的;它让你可以看看事务被创建和回滚在返回UnsupportOperationException实例中。

// the service interface that we want to make transactional

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

}
// an implementation of the above interface

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)必须在可以读写的事务上下文中执行。下面的配置将在下面几段中详细解释。

<!-- from the file '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">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- ensure that the above transactional advice runs for any execution
        of an operation defined by the FooService interface -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>

    <!-- don't forget the 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>
    <!-- similarly, don't forget the PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

</beans>

检查前面的配置。你想让使一个fooService对象具有事务性。应用事务语义是被封装在<tx:advice/>定义中。这个<tx:advice/>定义读作"…所有以‘get’开头的方法是被执行在可读事务上下文中并且所有其他方法是被执行在默认的事务语义中"。<tx:advice/>标签的transaction-manager属性是被设置成用来推动事务PlatformTransactionManager属性的名字,在本例中是txManager bean。

这个<aop:config/>定义确保transactional advice被定义由txAdvice bean会在程序的适当点上执行。首先定义一个匹配任何一个FooService接口方法的切点(名为fooServiceOperation)。然后使用一个advisor在切入点关联txAdvice。fooServiceoperation的执行结果表明,txAdvice将被执行。

<aop:pointcut/>元素中被定义的表达式是一个面向切面切点表达式;spring的切面表达式更多的详情查看第十章,spring的面向切面编程

一个常用需要是是使整个服务层事务。最好的方式是对切点表达式做一个简单吧的改变去匹配任何一个方法在你的服务层中。如下:

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

现在我们已经分析了配置,你可能会问自己,“好吧…但是这些配置实际上做了啥呢?”。

上面的配置将是被使用去创建一个事务的代理围绕着这个从fooService bean定义生成的对象。这个代理将被配置使用transactional advice,以便在代理上调用适当的方法时,一个事务是开始的、暂停的、标记为只可读的等等,依赖于与该方法关联的事务配置。考虑以下程序测试驱动上面的配置。

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

输出如下

<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[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]

<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[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 infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

回滚一个声明性事务

上节概述了在你的应用中如何为类(通常是服务层类)指定事务性配置的基础知识。这节介绍如何以一个简单的方式控制事务的回滚。

被推荐的方式是通过spring Framework的事务框架进行声明,那事务的工作将是被回滚通常在当前执行事务上下文的代码中抛出一个Exception。spring Framework的事务底层代码将捕获任何一个未被处理的异常(当它从调用栈冒出时),并确定是否将事务标记为回滚。

在其默认的配置中,Spring Framework的事务底层代码仅在捕获运行时的不需要检查的异常时标记一个事务回滚,也就是说当抛出一个RuntimeException异常时。(Errors也会-默认情况下-导致回滚)。默认配置下在一个事务方法上抛出检查异常不会回滚。

你可以准确的配置那些异常类型用来标记一个事务回滚,包括检查异常。以下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>

当一个异常被抛出时如果你不想对事务进行回滚,你也可以指定“不回滚规则”,如下:

<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 {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

你是被强烈的鼓励去尽可能使用声明式方法进行回滚。如果你绝对需要它,则可以使用编程式回滚,但它的使用是和实现一个干净的基于POJO框架背道而驰的。

为不同的bean配置不同的事务语义

考虑到这样一个场景,你有多个服务层对象,并且你想对它们应用完全不同的事务配置。你做这个由不同的定义通过<aop:advisor/>元素使用不同的pointcutadvice-ref属性值。

作为一个比较点,首先假设所有你的service层类都定义在一个根x.y.service包中。去使所有在包中(或者子包中)类定义并且以Service结尾命名的实例beans有默认的事务配置,你将编写如下:

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

    <!-- these two beans will be transactional... -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

    <!-- ... and these two beans won't -->
    <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>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

下面的例子展示如何配置两个使用完全不同事务配置的不同的beans。

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

    <!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this bean will also be transactional, but with totally different transactional settings -->
    <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>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

<tx:advice/>配置

这节总结了可以使用<tx:advice/>标签指定的各种事务配置。默认<tx:advice/>配置为:

  • Propagation配置是REQUIRED
  • Isolation级别是DEFAULT。
  • Transaction是read/write。
  • Transaction timeout默认底层事务系统的默认超时时间,如果timeout是不支持,则无。
  • 任何RuntimeException触发回滚,任何checked Exception都不会。

你可以改变这些默认的配置;<tx:method/>标签的不同属性是被嵌套在<tx:advice/><tx:attribute/>标签是总结如下:

AttributeRequired ?DefaultDescription
nameYes与事务属性相关联的方法名称。通配符()是被使用相同的事务属性设置与多种方法关联;例如get,handle*,on*Event,等等
propahationNoREQUIRED事务传播行为。
isolationNoDFAULT事务隔离级别。
timeoutNo-1事务超时值(以秒为单位)
read-onlyNofalse事务只读吗?
rollback-forNoException(s)触发回滚使用;分割。例如,com.foo.MyBusinessException,ServletException.
no-rollback-forNoException(s)不触发回滚使用;分割。例如com.foo.MyBusinessException,ServletException.

使用@Transactional

除了使用基于XML声明式对事务进行配置外,你可以使用一个基于注解方式。声明事务语义直接在java source code上声明更接近受影响的代码。不存在过度耦合的危险,因为在代码上使用事务是比较方便的。

使用@Transactional注解的易用最好以例子来说明,这下面的文本进行了解释。考虑一下类定义:

// the service class that we want to make 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);
}

当上述POJO被定义为一个在Spring IoC容器中的bean,bean实例可以被使用事务由仅添加一行事务配置:

<!-- from the file '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">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- (this dependency is defined somewhere else) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

</beans>

如果要连接的PlatformTransactionManager的bean名称具有名称transactionManager,则可以省略<tx:annotation-driven/>标记中的transaction-manager属性。如果您想要依赖注入的PlatformTransactionManager bean具有任何其他名称,那么您必须显示使用transaction-manager属性,如前面的示例中所示。

如果你使用基于java的配置,@EnableTransactionManagement注解式提供同样的支持。简单的添加在一个有@Configuration类上。详细的看javadocs。

方法可见性

当使用代理时,你应该应用@Transactional注解仅在public方法上。如果你注解在protected、private或者包可见性方法上,不会引发任何错误,但是被注解的方法并不会显示配置的事务设置。如果你需要注解non-public方法可以考虑使用AspectJ(见下文)。

你可以在接口定义上、接口方法上、类定义上或者类的public方法上使用注解。然而,仅存在@Transactional注解是不足够激活事务行为。@Transactional注解时简单元数据可以被消费由一些运行时框架,@Transactional-aware可以使用这个元数据去配置事务在适当的bean上。在之前的例子中,<tx:annotation-driven/>元素开启了事务行为。

Spring建议你只在具体的类上使用@Transactional注解(和具体类的方法)。你当然可以在一个接口(或者接口方法)上使用@Transactional注解,但是如果你使用基于接口代理,只能按照你期望的那样工作。java注解不是继承自接口的事实意味着你使用基于类的代理(proxy-target-class="true")或者基于编织切面(mode="aspectj",那么代理和编制底层框架是无法识别事务配置,并且对象是不被包裹在一个事务代理中,这绝对是糟糕的。
在代理模式下(默认的),只有通过代理进入外部方法调用才会被拦截。这意味着自身调用,实际上,一个方法在目标对象调用这目标对象的另一个方法,将不会导致一个实际代理在运行时就算这个调用方法被标记为@Transactional。此外代理必须被完全初始化去提供期望的行为,所以通过代理你应该不依赖这功能,即@PostConstruct

考虑到使用AspectJ模式(请参考下表中的属性)如果你期望自身调用也被包装事务。这个情况下,首先不会有代理;相反目标类是被编织(即其的字节码是被修改)以便@Transactional在任何类型方法上进入运行时行为。

tx:annotation-driven配置

XML AttributeAnnotation AttributeDefaultDescription
transaction-managerN/A (See TransactionManagementConfigurer javadocs)transactionManager使用事务管理名称。仅当事务管理名称不是transactionManager是被需要,如上所示。
modemodeproxy默认的模式“proxy”处理事务的AOP框架代理被注解的bean(遵循代理语义上,以上所述,仅适用通过代理传入的方法调用)。另外一种“aspect”模式而是将受影响的类和Spring的AspectJ事务aspect,将修改这目标类字节码去应用在不同种类的回调上。AspectJ编译需要在classpath中存在spring-aspects.jar以及启用加载时翻译(或者及时编译)。(参看 the section called “Spring configuration”这节了解详细的设置加载时编译)。
proxy-target-classproxyTargetClassfalse仅适用于代理模式。用于控制被@Transactional注解的类创建什么类型的事务代理。如果proxy-target-class属性是被设置为true,然后基于类的代理被创建。如果proxy-target-class是false或者这个属性是被忽略,则标准的JDK基于接口代理是被创建。(有关不同代理类型的详细查阅,请参见第 10.6 节“代理机制”。)
orderorderOrdered.LOWEST_PRECEDENCE定义被应用于使用@Transactional注解的bean的事务advice顺序。(关于更多的关联AOP advice顺序的规则信息,请参阅the section called “Advice ordering”.。)没有指定排序意味着AOP子系统决定advice的排序。)

@EnbleTransactionManagement和<tx:annotation-drive/>仅寻找定义在相同的应用上下文的beans上的@Transactional。这意味着如果你把注解驱动配置放在一个DispatcherServlet的webApplicationContext中,而不是你的services。请参阅第 21.2 节,“DispatcherServlet”

在评估方法的事务设置时,底层位置优先。下面的例子中,DefaultService类被注解在这类级别上设置只读事务,但是这个注解@Trasactional在这updateFoo(Foo)方法上比同样的注解定义事务更优先。

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

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

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

@Transactional配置

@Transactional注解是一个指定一个接口、类、方法必须有事务语义;例如当一个方法被调用时开始一个新的只读事务,暂停任何一个存在的事务。@Transactional默认配置如下:

AttributeTypeDescription
valueString指定要使用的transaction manager的可选限定符。
propagationenum:Propagation事务传播行为。
isolationenum:Isolation
timeoutint事务只读的吗?
read-onlyboolean事务是只读的吗?
rollback-forclass对象数组。类必须继承Throwable。Exception(s)触发回滚使用;分割。例如com.foo.MyBusinessException,ServletException.
no-rollback-forlass对象数组。类必须继承ThrowableException(s)不触发回滚使用;分割。例如com.foo.MyBusinessException,ServletException.

当前你无法明确控制事务的名称,这个名称意味着这个事务名称是被展示在一个transaction monitor,如果合适(例如WebLogic的transaction monitor)并在日志中输出。关于声明性事务,事务名称总是全限定类名+“.”+transactionally-advised类的方法名。例如,如果BussinessService类的handlePayment(…)方法开始一个事务,这个事务名将是:com.foo.BusinessService.handlePayment。

使用@Transactional的多个事务管理器

大多数Spring 应用程序仅需要一个单事务管理器,但可能存在你需要在单个应用程序中使用多个独立的事务管理器的情况。@Transaction注解的属性值可以选择性的指定PlatformTransactionManager标识。这个标识可以是transaction manager bean的名字或者qualifier值。例如,在下面的java代码中使用

public class TransactionalService {

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

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

可结合以下application context的事务管理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"标识区分。如果未找到限定的PlatformTransactionManager bean,默认<tx:annotation-driven>目标 bean名称transactionManager将是被使用。

自定义快捷方式注解

如果你发现你需要在不同的方法上重复使用相同的@Transaction属性,那么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() { ... }
}

这里我们使用语法定义事务管理限定符,但也可以包含propagation behavior、rollback rules、timeouts等。

事务传播

这节介绍Spring中的事务传播的一些语义。请注意,本节不是一个关于事务传播的本身介绍;而是说明关于Spring中事务传播的一些语义。

在Spring管理的事务中,注意物理和逻辑事务的不同,以及如何设置传播应用于这种区别。

Required

在这里插入图片描述
PROPAGATION_REQUIRED
当传播设置是PROPAGATION_REQUIRED时,将应用设置的每个方法创建一个逻辑事务范围。每个这样的逻辑事务范围可以单独确定仅回滚状态,外部事务范围逻辑上可以独立于内部事务范围。当然在标准的PROPAGATION_REUIRED行为,所有这些范围将是被映射到相同的逻辑事务。所以在内部事务范围设置一个仅回滚标识会影响事务实际提交的机会(如你期望的那样)。

但是,在内部事务作用域设置仅回滚的标识的情况下,外部事务没有自己决定的回滚,因此这回滚是意外的(由内部事务作用域以静默方式触发)。在这点上抛出一个对应UnexceptionRollbackException。这是被期望的行为,因此事务调用者永远不会被误导去认为一个提交是被执行而实际上并未被执行。所以如果一个内部事务(外部调用者是不知道)静默标记事务仅回滚,这外部事务仍然调用提交。外部调用者需要收到一个UnexceptionRollbackException以清楚的表明改为执行回滚。

RequiresNew

在这里插入图片描述
PROPAGATION_REQUIRES_NEW和PROPAGATION_REQUIRED对比,对每个影响的事务范围使用一个完全独立事务。在这种情况下,底层事务是不同的所以可以独立的提交和回滚,外部的事务不被内部事务的回滚状态影响。

Nested

PROPAGATION_NESTED使用一个带多个可以回滚的事务的保存点单物理事务。这种局部回滚运行一个内嵌事务在它的范围内触发一个回滚,并且外部事务是可以继续物理事务尽管一些操作是被回滚。这个设置是通常被映射在JDBC安全点,所以将仅在JDBC支持这样的事务下生效。请参阅Spring的DataSourceTransactionManager。

Advising transactional operations

假设你想执行事务和一些基础profiling advice。你如何实现这点在context的<tx:annotation-driven/>标签上设置呢?

当你想调用updateFoo(Foo)方法时,你想看以下操作:

  • 配置好的profiling aspect启动
  • 执行transactional advice
  • 方法在advised对象上执行
  • 事务提交
  • profiling aspect报告准确的整个事务方法调用持续时间

这张不涉及详细解释AOP(除非它适用于事务)。请参考第十章,Spring的面向切面编程以获取更详细的AOP和AOP总体介绍。

这里上面讨论的一个简单的profiling aspect代码。advice的顺序是被控制通过Ordered接口。更详细的Advice顺序请参阅“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;

    // allows us to control the ordering of advice
    public int getOrder() {
        return this.order;
    }

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

    // this method is the around advice
    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"/>

    <!-- this is the aspect -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" __value="1"__/>
    </bean>

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

    <aop:config>
            <!-- this advice will execute around the transactional advice -->
            <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已有profiling和事务的切面使用需要的顺序。你可以使用类似方式配置任何数量的额外切面。

下面的例子和上面的配置是一样的作用,但是使用纯XNL声明方式。

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

    <!-- the profiling advice -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        __<property name="order" value="1__"/>
    </bean>

    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
        <!-- will execute after the profiling advice (c.f. the order attribute) -->

        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" __order="2__"/>
        <!-- order value is higher than the profiling aspect -->

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

    <!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->

</beans>

如果你想使用这种方式让profiling advice在事务advice之后执行或者在事务advice之前执行,那么你可以把profiling aspect bean的order属性值换成比transactional advice的order更高的值就行。

通过AspectJ使用@Transactional

还可以通过AspectJ在Spring container之外使用Spring Framework的@Transactional的支持。为此,你首选使用@Transactional注解注释你的类(也可以是你的类方法上),然后你使用定义在spring-aspects.jar包中的org.springframework.transaction.aspectj.AnnotationTransactionAspect连接你的应用。这个aspect必须被使用transaction manager配置。你当然可以使用Spring Framework的IOC容器去依赖注入这个aspect。最简单的方式是配置使用<tx:annotation-driven/>元素的mode属性值为aspect来依赖注入transaction management aspect如使用@Transactional中的描述。因为我们在这里关注的是Spring容器之外的运行的应用程序,所以我们将向你展示如何以编程的方式实现它。

// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);

当你使用Aspect时,你必须注解实现类(并且或者注解在类的方法上)而不能在类的实现接口上。Aspect遵循接口上的注解是不能被继承的java规则

类上的@Transactional注解为该类的任何方法上的执行指定了默认事务语义。方法上的@Transactional注解覆盖类上@Transactional注解的默认事务语义(如果存在)。任何一个方法可以使用@Transactional不用管可见性。

去使用AnnotationTransactionAspect编织你的应用,你必须使用AspectJ(参阅AspectJ 开发指南)或者运行时编译构建你的应用。请参见第 10.8.4 节“在 Spring 框架中使用 AspectJ进行运行时编译”

编程式事务管理

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

  • 使用TransactionTemplate
  • 直接使用PlatformTransactionManager实现

Spring团队通常建议你使用TransactionTemplate用于编程式事务管理。这第二种是类似于使用JTA UserTransaction API,虽然处理异常没有那么笨重。

使用TransactionTemplate

TransactionTemplate使用和其他的Spring templates相同的方式例如JdbcTemplate。它使用一个回滚方式,去释放应用代码由必须获取和释放引用的事务资源,并且结果在代理中根据意图产生,因此开发者可以按照自己的意图编写代码。

如你下面的例子所看到的,使用TransactionTemplate明显配合你与Spring’s transaction infrastructure和API结合在一起。编程式事务管理是否合适取决于你的开发需要是你自己必须决定的。

代码应该必须执行在一个事务的上下文,并且将显示的使用TransactionTemplate,如下所示。你作为一个应用开发者,编写一个TransactionCallback实现类(通常表示为一个匿名内部类)包含着你需要在一个事务上下文中执行的代码。然后你通过你定制的TransactionCallback去执行execute(…)方法暴露在TransactionTemplate。

public class SimpleService implements Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private final TransactionTemplate transactionTemplate;

    // use constructor-injection to supply the 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() {
            // the code in this method executes in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

如果这里没有返回值,使用匿名的便捷TransactionCallbackWithoutResult类,如下:

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

回调的代码可以使用setRollbackOnly()回滚事务使用提供的TransactionStatus对象中:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

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

定制事务配置

你可以指定事务配置模式、隔离级别、超时等,通过使用Transactionplate配置或者在配置中。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);

        // the transaction settings can be set here explicitly if so desired
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        this.transactionTemplate.setTimeout(30); // 30 seconds
        // and so forth...
    }
}

以下示例使用Spring XML配置定义了TransactionTemplate使用了自定义一设置。shareTransactionTemplate可以按照所需注入到service中。

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

最后,Transactiontemplate类实例是线程安全的,因此实例不维护任何会话状态。然而TransactonTemplate维护配置状态,所以当一些类也许共享TransactionTemplate单例实例,如果一个类需要使用不同的配置的TransactionTemplate,那么你需要创建两个不同的TransactionTemplate实例。

使用PlatformTransactionManager

你可以使用org.springwork.transaction.PlatformTransactionManager直接管理你的事务。简单的通过PlatformTransactionManager实例,通过bean引用使用在你的bean上。然后使用TransactionDefinition和TransactionStatus对象进行开始、回滚、提交。

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

在编程式和声明式事务管理之间进行选择

当你有少量的事务操作时,编程式事务管理通常是一个好的注意。例如,如果你有一个web应用仅某些更新操作需要事务,你也许不想设置事务代理使用Spring或者任何其他技术。在这种情况下,使用TransactionTemplate也许是一个好的方法。能够显示设置事务名称也是只能使用编程方式管理事务能够完成的事情。

事件绑定事件

从Spring4.2开始,事务监听可以绑定到事务的某个阶段。典型的例子是当事务成功完成时处理事件:当前事务结果实际上对监听很重要时,允许事件被更加灵活的使用。

注册一个常规事件监听是通过@EventListener注解。如果你需要将其绑定到当前事务,请使用@TransactionalEventListener。这样做listener将默认绑定到事务提交阶段。

让我们举个例子去阐明这个概念。假设一个component发布一个订单创建事件并且我们定义一个监听器,该监听器应该在事务提交成功后触发:

@Component
public class MyComponent {

    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
          ...
    }
}

TransactionalEventListener注解公开一个phase属性允许你定制事务的不同阶段去和监听器绑定。BEFORE_COMMIT、AFTER_COMMIT、AFTER_ROLLBACK和AFTER_COMPLETION(无论是提交还是回滚)。

如果没有事务正在运行,则根本不会调用侦听器,因为我们无法遵守所需的语义。但是,可以通过将fallbackExecution注解的属性设置为true来覆盖该行为。

应用服务器特定继承

Spring的事务抽象通常是和应用服务无关的。此外,Spring 的 JtaTransactionManager 类可以选择对 JTA UserTransaction 和 TransactionManager 对象执行 JNDI 查找,自动检测后者对象的位置,该位置因应用程序服务器而异。对JTA事务的访问运行增强事务语义,特别是支持事务暂停。详细的请参阅JtaTransactionManager javadoc。

Spring的JtaTransactionManger是在JavaEE应用程序上运行的标准选择,并且可以运行在所有常见服务。先进的功能如事务暂停也适用于许多服务器——包括GlassFish、JBoss、Geronimo不需要任何特殊的配置。然而,要完全支持事务暂停和更多的高级功能,Spring为WebLogic和WebSphere提供了特殊的适配器。以下章节将讨论这些适配器。

对于标准方案,包括WebLogic Server和WebSphere,考虑使用方便的<tx:jta-transaction-manager/>配置元素。配置后,这个元素自动检测底层服务并选择平台可用最佳的事务管理。这意味着你不必明确配置特定的服务适配器类(如下面章节所述)。相反,使用标准JtaTransactionManager作为默认后备,他们是自动选择的。

IBM WebSphere

WebSphere 6.1.0.9及更高版本上,推荐使用的Spring JTA事务管理是WebSphereUowTransactionmanager。这个特殊的适配器利用了IBM的UOWManager API,UOWManager API在WebSphere Application Server 6.0.2.19和更高版本以及6.1.0.9和更高版本。有了这个适配器,Spring-driven事务挂起(由PROPAGATION_REQUIRES_NEW导致的挂起和恢复)是正式由IBM支持的。

Oracle WebLogic server

WebLogic Server 9.0或者以上版本,你通常会使用WebLogicJtaTransactionManager代替备选的JtaTransactionManager类。这个特别的WebLogic-specific的正规JtaTransactionManager子类支持在WebLogic-managed事务环境中的Spring的事务定义的全部功能,超越标准的JTA语义:特性包括事务名称、每个事务隔离等级以及所有情况下的正确恢复事务。

常见问题的解决方案

对特定数据源使用错误的事务管理

根据你选择的事务技术和要求使用正确的PlatformTransactionManager实现。使用适当的,Spring Framework仅提供简单且适合的抽象。如果你在使用全局事务,则必须使用org.springframework.transaction.jta.JtaTransactionManager类(或者一个应用服务特定的JtaTransactionManager子类)用于所有你的事务操作。否则,事务基础架构会尝试在诸如容器 DataSource 实例之类的资源上执行本地事务。 这样的本地事务没有意义,好的应用服务器会将它们视为错误。

更多资源

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值