Spring中的事务管理(官方文档翻译)

翻译:官方文档

1 事务管理

Spring框架提供的强大的功能之一就是其全面的事务管理能力。Spring框架为事务管理提供了统一的抽象层,有以下几方面的好处:

  • 在不同的事务管理APIs之间建立了一致的编程模型。这些事务管理APIs包括了Java Transaction APIJTA)、JDBCHibernateJava Persistence APIJPA)。
  • 支持声明式事务管理
  • 比各种各样的事务管理APIs更加容易使用。
  • Spring数据访问抽象层更好地集成。

下面几节将会详细地描述Spring事务管理的特性的技术:

(本章还包括了最佳实践的讨论:应用服务器集成常见问题的解决办法)。

1.1 Spring框架事务管理模型的优势

传统上,Java EE开发者们有两种事务管理可选择:全局事务管理或者本地事务管理。这两个选择都有一定的局限性。在接下来的两节中本文将对全局和本地事务管理进行回顾,然后讨论Spring Framework提供的事务管理模型如何解决全局和本地事务管理模型的局限性。

1.1.1 全局事务管理

全局事务管理允许你使用多个数据源,通常是关系型数据库和消息队列。应用程序服务器通过JTA来管理全局事务。但是JTA本身是一个相当笨重的API,并且一个JTAUserTransaction通常需要从JNDI获取,这意味着你还要提供JNDI才能使用JTA。全局事务的使用限制了应用程序代码的任何潜在重用,因为JTA通常仅在应用程序服务器环境中可用。

以前,使用全局事务的首选方法是通过EJB CMT(容器管理事务)。CMT是一种声明式事务管理形式(区别于编程式事务管理)。EJB CMT消除了对与事务相关的JNDI查找的需要,尽管EJB本身的使用需要JNDI。它消除了大部分(但不是全部)编写Java代码来控制事务的需要。但是它的显著缺点是CMTJTA和应用服务器环境绑定在一起。而且,只有在选择在EJB中实现业务逻辑时(或者至少在事务EJB facade之后),它才可用。EJB的缺点通常是如此之大,以至于这不是一个有吸引力的选择,尤其是在面对声明性事务管理的强制性替代方案时。

1.1.2 本地事物管理

本地事务是特定于资源的,例如与JDBC连接关联的事务。本地事务可能更容易使用,但有一个明显的缺点:它们不能跨多个事务资源工作。例如,使用JDBC连接管理事务的代码不能在全局JTA事务中运行。由于应用服务器不参与事务管理,因此它无法帮助确保跨多个资源的正确性。(值得注意的是,大多数应用程序使用单个事务资源。)另一个缺点是本地事务对编程模型具有侵入性。

1.1.3 Spring框架提供的一致的事务管理模型

Spring同时解决了全局事务和本地事务的缺点。它允许应用程序开发人员在任何环境中使用一致的事务管理编程模型。您只需编写一次代码,它就可以从不同环境中的不同事务管理策略中获益。并且Spring框架提供了声明式和编程式事务管理功能。大多数用户更喜欢声明式事务管理,这是我们在大多数情况下推荐的。

在使用编程式事务管理的时候,开发人员可以使用Spring Framework的事务抽象,它可以运行在任何底层事务基础架构上。使用首选的声明式模型,开发人员通常只需要编写很少或根本不编写与事务管理相关的代码,因此不依赖于Spring Framework事务API或任何其他事务API

1.2 理解Spring框架对事务管理的抽象

Spring对事务管理的抽象关键在于对事务策略的抽象。事务策略由org.springframework.transaction.PlatformTransactionManager接口定义,下面是相关源码:

扫描二维码关注公众号,回复: 5095311 查看本文章
public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

这主要是一个服务提供者接口(SPI),尽管你可以从应用程序代码中以编程的方式使用它。因为PlatformTransactionManager是一个接口,所以可以根据需要轻松地mock它。它不与查找策略(如JNDI)绑定。PlatformTransactionManager的实现类与Spring Framework IoC容器中的任何其它对象(或bean )一样被定义。仅这一点就使Spring Framework事务成为一个有价值的抽象,即使您使用JTA时也是如此。与直接使用JTA相比,你可以更容易地测试事务相关的代码。

同样,按照Spring的哲学,由任何PlatformTransactionManager接口的方法抛出的TransactionException是非受检异常(也就是说,它扩展了java.lang.RuntimeException类)。事务基础设施故障几乎总是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获和处理TransactionException。重要的一点是,开发人员不是被迫这样做的。

getTransaction(..)方法根据TransactionDefinition参数返回一个TransactionStatus对象。如果当前调用堆栈中存在匹配的事务,则返回的TransactionStatus可能表示一个新事务,也可能表示一个现有事务。后一种情况的含义是,与Java EE事务上下文一样,TransactionStatus与执行线程相关联。

TransactionDefinition接口指定:

  • 传播行为:通常,在事务范围内执行的所有代码都在该事务中运行。但是,如果事务方法是在事务上下文已经存在的情况下执行的,则可以指定传播特性。例如,代码可以在现有事务中继续运行(通常情况下),或者可以挂起现有事务并创建一个新事务。Spring提供了EJB CMT中熟悉的所有事务传播特性选项。要了解Spring中事务传播的语义,请参见事务的传播特性
  • 隔离级别:此事务与其他事务的工作隔离的程度。例如,这个事务可以看到来自其他事务的未提交的写吗?
  • 事务超时:被自动回滚之前事务运行的超时时间。
  • 只读状态:当代码只读取但不修改数据时,可以指定只读事务。在某些情况下,只读事务可能是一种有用的优化,比如在使用Hibernate时。

这些设置反映了标准的事务概念。如果有必要,请参阅讨论事务隔离级别和其他核心事务概念的参考资料。理解这些概念对于使用Spring框架或任何事务管理解决方案都是至关重要的。

TransactionDefinition接口的定义如下:

public interface TransactionDefinition {

	int PROPAGATION_REQUIRED = 0;

	int PROPAGATION_SUPPORTS = 1;

	int PROPAGATION_MANDATORY = 2;

	int PROPAGATION_REQUIRES_NEW = 3;

	int PROPAGATION_NOT_SUPPORTED = 4;

	int PROPAGATION_NEVER = 5;

	int PROPAGATION_NESTED = 6;

	int ISOLATION_DEFAULT = -1;

	int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

	int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;

	int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

	int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

	int TIMEOUT_DEFAULT = -1;

	int getPropagationBehavior();

	int getIsolationLevel();

	int getTimeout();

	boolean isReadOnly();

	@Nullable
	String getName();
}

TransactionStatus接口为事务代码提供了一种控制事务执行和查询事务状态的简单方法。这些概念应该很熟悉,因为它们对所有事务api都是通用的。下面的清单显示了TransactionStatus接口的定义:

public interface TransactionStatus extends SavepointManager, Flushable {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    @Override
    void flush();

    boolean isCompleted();

}

无论您在Spring中选择声明式事务管理还是编程式事务管理,定义正确的PlatformTransactionManager接口的实现都是绝对必要的。您通常通过依赖注入来定义此实现。

PlatformTransactionManager接口的实现通常需要了解其工作环境的知识:JDBCJTAHibernate等等。下面的示例展示了如何定义本地PlatformTransactionManager的实现(在本例中,使用普通JDBC)。

您可以通过创建类似如下的bean来定义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获得的容器数据源与SpringJtaTransactionManager连接。下面的示例显示了JTAJNDI查找版本:

<?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不需要知道数据源(或任何其他特定资源),因为它使用容器的全局事务管理基础设施。

上边的定义中的数据源bean使用来自jee名称空间的<jndi-lookup/>标记。有关更多信息,请参见JEE模式

您还可以使用Hibernate本地事务,如下面的示例所示。在这种情况下,您需要定义一个HibernateLocalSessionFactoryBean,应用程序代码可以使用它来获取HibernateSession实例。

DataSourcebean定义类似于前面显示的本地JDBC示例,因此在下面的示例中不显示。

如果DataSource(由任何非jta事务管理器使用)通过JNDI查找并由Java EE容器管理,那么它应该是非事务性的,因为管理事务的是Spring框架(而不是Java EE容器)。

本例中的txManager bean是HibernateTransactionManager类型。与DataSourceTransactionManager需要对DataSource的引用一样,HibernateTransactionManager需要对SessionFactory引用。下面的例子声明了sessionFactorytxManager bean:

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

如果您使用HibernateJava EE容器管理的JTA事务,那么对于JDBC,您应该使用与前一个JTA示例相同的JtaTransactionManager,如下面的示例所示:

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

如果您使用JTA,您的事务管理器定义应该看起来是相同的,无论您使用什么数据访问技术,无论是JDBCHibernate JPA还是任何其他受支持的技术。这是因为JTA事务是全局事务,它可以征募任何事务资源。

在所有这些情况下,应用程序代码都不需要更改。您可以仅通过更改配置来更改事务的管理方式,即使更改意味着从本地事务转移到全局事务,或者反之亦然。

1.3 通过事务同步资源

现在你应该清楚如何创建不同的事务管理器,以及如何将它们链接到需要同步到事务的相关资源上(例如DataSourceTransactionManagerJDBCDataSourceHibernateTransactionManagerHibernateSessionFactory,等等)。本节描述应用程序代码(直接或间接地,通过使用JDBCHibernateJPA等持久性API)如何确保正确地创建、重用和清理这些资源。本节还讨论了如何通过相关的PlatformTransactionManager(可选地)触发事务同步。

1.3.1 高级的同步方法

建议首选的方法是使用Spring最高级别的基于模板的持久性集成api,或者使用具有事务感知的工厂bean或代理的本机ORM api来管理本机资源工厂。这些事务感知解决方案在内部处理资源的创建和重用、清理、资源的可选事务同步和异常映射。因此,用户数据访问代码不必处理这些任务,而可以只关注非样板持久性逻辑。通常,您可以使用本机ORM API,或者通过使用JdbcTemplate采用JDBC访问的模板方法。这些解决方案将在本参考文档的后续章节中详细介绍。

1.3.2 低级的同步方法

诸如DataSourceUtils(用于JDBC)、EntityManagerFactoryUtils (用于JPA)、SessionFactoryUtils (用于Hibernate)等类属于较低级别的。当您希望应用程序代码直接处理本机持久性API的资源类型时,您可以使用这些类来确保获得适当的Spring框架管理的实例、事务(可选地)同步以及流程中发生的异常被正确地映射到一致的API

例如,在JDBC的情况下,您可以使用Springorg.springframework.jdbc.datasource.DataSourceUtils 类来代替在DataSource上调用getConnection()方法的传统JDBC方法,如下所示:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有事务已经有了与之同步(链接)的连接,则返回该实例。否则,方法调用将触发新连接的创建,该连接将(可选地)同步到任何现有事务,并在同一事务中供后续重用。如前所述,任何SQLException都被包装在Spring框架CannotGetJdbcConnectionException中,这是Spring框架中非受检的DataAccessException类型的层次结构之一。这种方法提供了比从SQLException容易获得的信息更多的信息,并确保了跨数据库甚至跨不同持久性技术的可移植性。

这种方法在没有Spring事务管理的情况下也可以工作(事务同步是可选的),因此无论是否使用Spring进行事务管理,都可以使用它。

当然,一旦您使用了SpringJDBC支持、JPA支持或Hibernate支持,您通常不喜欢使用DataSourceUtils或其他帮助类,因为与直接使用相关api相比,您更乐于使用Spring的抽象。例如,如果您使用Spring的 JdbcTemplatejdbc.object包去简化JDBC的使用,需要在后台进行正确的连接检索,不需要编写任何特殊的代码。

1.3.3 TransactionAwareDataSourceProxy

在最底层存在一个TransactionAwareDataSourceProxy类。这是目标DataSource的代理,它包装目标DataSource以添加Spring管理事务的感知。在这方面,它类似于由Java EE服务器提供的事务JNDI DataSource

除非必须调用现有代码并传递标准JDBC DataSource 接口实现,否则几乎不应该需要或希望使用这个类。在这种情况下,这段代码可能是可用的,但是参与了spring管理的事务。您可以使用前面提到的高级抽象来编写新代码。

1.4 声明式事务管理

大多数Spring框架用户选择声明式事务管理。此选项对应用程序代码的影响最小,因此最符合非入侵轻量级容器的理想。

Spring框架的声明式事务管理通过Spring面向切面编程(AOP)得以实现。然而,由于Spring框架发行版附带了事务方面的代码,并且可能以样板形式使用,所以通常不需要理解AOP概念就可以有效地使用这些代码。

Spring框架的声明式事务管理类似于EJB CMT,因为您可以将事务行为(或缺少事务行为)指定到各个方法级别。如果需要,可以在事务上下文中进行setRollbackOnly()调用。这两种事务管理类型之间的区别是:

  • 与绑定到JTAEJB CMT不同,Spring框架的声明式事务管理可以在任何环境中工作。它可以通过调整配置文件来使用JDBCJPAHibernate处理JTA事务或本地事务。
  • 您可以将Spring Framework声明式事务管理应用于任何类,而不仅仅是EJB等特殊类。
  • Spring框架提供了声明式回滚规则,这是一个没有EJB等效项的特性。提供了对回滚规则的编程和声明支持。
  • Spring框架允许您使用AOP自定义事务行为。例如,您可以在事务回滚的情况下插入自定义行为。您还可以添加任意的通知以及事务通知。使用EJB CMT,您不能影响容器的事务管理,除非使用setRollbackOnly()
  • Spring框架不像高端应用服务器那样支持跨远程调用传播事务上下文。如果您需要这个特性,我们建议您使用EJB。但是,在使用这种特性之前要仔细考虑,因为通常不希望事务跨越远程调用。

TransactionProxyFactoryBean在哪?

Spring 2.0及以上版本中的声明式事务配置与Spring以前的版本有很大不同。主要区别在于不再需要配置TransactionProxyFactoryBean

Spring 2.0之前的配置样式仍然是100%有效的配置。可以将新的<tx:tags/>视为代表您定义的TransactionProxyFactoryBean

回滚规则的概念非常重要。它们允许您指定哪些异常(和可抛出的异常)应该导致自动回滚。您可以在配置中,而不是在Java代码中,以声明的方式指定它。因此,尽管您仍然可以在TransactionStatus对象上调用setRollbackOnly()来回滚当前事务,但通常您可以指定MyApplicationException必须始终导致回滚的规则。此选项的显著优点是业务对象不依赖于事务基础结构。例如,它们通常不需要导入Spring事务api或其他Spring api

尽管EJB容器默认行为会自动回滚系统异常(通常是运行时异常)上的事务,但EJB CMT不会自动回滚应用程序异常(即,除了java.rmi.RemoteException之外的受检异常)上的事务。虽然声明式事务管理的Spring默认行为遵循EJB约定(回滚仅在非受检的异常上是自动的),但是定制这种行为通常很有用。

1.4.1 理解Spring框架声明式事务的实现

仅仅告诉您使用@Transactional注释注释类、将@EnableTransactionManagement添加到配置中并期望您理解它是如何工作的是不够的。为了提供更深入的理解,本节解释Spring Framework的声明式事务基础设施在事务相关问题发生时的内部工作方式。

关于Spring框架的声明式事务支持,需要掌握的最重要的概念是,这种支持是通过AOP代理启用的,事务通知是由元数据(当前基于XML或注释)驱动的。AOP与事务元数据的结合产生了一个AOP代理,它使用一个TransactionInterceptor和一个合适的PlatformTransactionManager实现来驱动方法调用周围的事务。

AOP章节将介绍Spring AOP

下面的图像展示了在事务代理上调用方法的概念视图:

在这里插入图片描述

1.4.2 声明式事务实现的例子

考虑以下接口及其伴随的实现。这个例子使用FooBar类作为占位符,这样您就可以专注于事务的使用,而不必关注特定的域模型。对于本例来说,DefaultFooService类在每个实现的方法体中抛出UnsupportedOperationException实例的事实是好的。该行为允许您看到事务被创建,然后回滚以响应UnsupportedOperationException实例。下面的清单显示了FooService接口:

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

}

下面的例子展示了前面接口的实现:

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)必须在具有读写语义的事务的上下文中执行。下面几段将详细解释如何配置:

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

    <!-- 我们希望添加事务管理的service对象 -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- 事务通知 (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- 事务语义... -->
        <tx:attributes>
            <!-- 所有以get开头的方法只能在只读语义的事务上下文中执行 -->
            <tx:method name="get*" read-only="true"/>
            <!-- 其他的方法使用默认的配置 (see below) -->
            <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>

如果要连接的PlatformTransactionManagerbean名称为transactionManager,则可以忽略事务通知(<tx:advice/>)中的transaction-manager属性。如果您想要连接的PlatformTransactionManager bean有任何其他名称,则必须显式地使用transaction-manager属性,如前面的示例所示。

<aop:config/>定义确保txAdvice bean定义的事务通知在程序中的适当位置执行。首先,定义一个切入点,它匹配在FooService接口(fooServiceOperation)中定义的任何操作的执行。然后使用advisor工具将切入点与txAdvice关联起来。结果表明,在执行fooServiceOperation时,将运行txAdvice定义的通知。

<aop:pointcut/>元素中定义的表达式是一个AspectJ切入点表达式。有关Spring中的切入点表达式的更多细节,请参阅AOP部分

一个常见的需求是使整个服务层具有事务性。最好的方法是改变切入点表达式以匹配服务层中的任何操作。下面的示例展示了如何这样做:

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

在前面的示例中,假设您的所有服务接口都在x.y.service包中定义。有关更多细节,请参见AOP部分

现在我们已经分析了配置,您可能会问自己,所有这些配置实际上做了什么?

前面显示的配置用于围绕从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 container 启动... -->
[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)

1.4.3 回滚声明式事务

上一节概述了如何在应用程序中以声明方式为类(通常是服务层类)指定事务设置的基础知识。本节描述如何以简单的声明式方式控制事务回滚

要向Spring Framework的事务基础结构指示要回滚事务的工作,建议的方法是从当前在事务上下文中执行的代码中抛出异常Spring Framework的事务基础结构代码在调用堆栈中产生气泡时捕捉任何未处理的异常,并决定是否将事务标记为回滚。

在其默认配置中,Spring Framework的事务基础结构代码将事务标记为仅在运行时非受检异常的情况下回滚。也就是说,当抛出的异常是RuntimeException的实例或子类时。(默认情况下,错误实例也会导致回滚)。从事务方法抛出的受检异常不会导致默认配置中的回滚。

您可以准确地配置哪些异常类型将事务标记为回滚,包括选中的异常。下面的XML片段演示了如何为已检查的特定于应用程序的异常类型配置回滚:

<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框架的事务基础架构捕获异常并参考配置的回滚规则以确定是否将事务标记为回滚时,最匹配的规则获胜。因此,在以下配置的情况下,除了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) {
        // 以编程方式触发回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

如果可能的话,强烈建议您使用声明的方式指定回滚。如果您确实需要,可以使用编程式回滚,但是它的使用与实现干净的基于pojo的体系结构背道而驰。

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

考虑这样一个场景:您有许多服务层对象,并且您希望对每个服务层对象应用完全不同的事务配置。可以通过定义不同的<aop:advisor/>元素,这些元素具有不同的切入点和advice-ref属性值。

作为比较,首先假设您的所有服务层类都定义在根x.y.service包中。要使包(或子包)中定义的类实例以及名称以Service结尾的所有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="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"/> <!-- (这个bean不在匹配的包里面) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (这个bean不是以Service结尾) -->

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

    <!-- 省略了其他事务基础架构bean,如 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将会被事务管理 (see the 'defaultServiceOperation' pointcut) -->
    <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>

    <!-- 省略了其他事务基础架构bean,如 PlatformTransactionManager ... -->

</beans>

1.4.5 <tx:advice/>可配置项

本节总结了使用<tx:advice/>标签的各种事务配置项。<tx:advice/>的默认配置是:

  • 传播行为默认为REQUIRED
  • 隔离级别是DEFAULT
  • 事务默认是可读写的。
  • 事务超时默认为基础事务系统的默认超时,如果不支持超时,则为none
  • 任何RuntimeException都会触发回滚,而任何受检的异常都不会。

你可以改变这些默认配置。下表总结了嵌套在<tx:advice/><tx:attributes/>标签中的<tx:method/>标签的各种属性:

<tx:method/>设置:

属性 是否必须 默认值 描述
name 与事务属性关联的方法名。通配符(*)字符可用于将相同的事务属性设置与许多方法关联(例如get*handle*on*Event等)。
propagation REQUIRED 事务传播行为。
isolation DEFAULT 事务隔离级别。仅适用于REQUIREDREQUIRES_NEW的传播设置。
timeout -1 事务超时(秒)。仅适用于传播REQUIREDREQUIRES_NEW
read-only false 读写事务与只读事务。仅适用于REQUIREDREQUIRES_NEW
rollback-for 触发回滚的用逗号分隔的异常实例列表。例如,com.foo.MyBusinessException,ServletException
no-rollback-for 不触发回滚的用逗号分隔的异常实例列表。例如,com.foo.MyBusinessException,ServletException

1.4.6 使用@Transactional

除了基于xml的事务配置声明方法外,还可以使用基于注释的方法。在Java源代码中直接声明事务语义使声明更接近受影响的代码。不存在过度耦合的危险,因为用于事务处理的代码几乎总是以这种方式部署的。

标准javax.transaction。事务性注释也支持作为Spring自身注释的替代。有关详细信息,请参阅JTA 1.2文档

使用@Transactional 注解所提供的易用性最好通过一个示例进行说明,该示例将在下面的文本中进行解释。考虑以下类定义:

// 需要进行事务管理的Service类
@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上下文中定义为bean时,您可以通过@Configuration类中的@EnableTransactionManagement注解使bean实例具有事务性。有关详细信息,请参见javadoc

在XML配置中,<tx:annotation-driven/>标记提供了类似的便利:

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

    <!-- 需要被事务管理的Service实例 -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- 开启基于注解的事务管理 -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- 需要指明 PlatformTransactionManager 接口的实现类 -->

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- ( dataSource 依赖在别的地方定义了 ) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

</beans>

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

您可以将@Transactional注释应用于接口定义、接口上的方法、类定义或类上的公共方法。然而,仅仅存在@Transactional注释不足以激活事务行为。@Transactional注释只是一些运行时基础设施可以使用的元数据,这些运行时基础设施能够感知@Transactional,可以使用元数据配置具有事务行为的适当bean。在前面的示例中,<tx:annotation-driven/>元素在事务行为上进行切换。

Spring团队建议使用@Transactional注释只注释具体类(和具体类的方法),而不是注释接口。您当然可以将@Transactional注释放置在接口(或接口方法)上,但是只有在使用基于接口的代理时,它才能正常工作。Java注释没有从接口继承这一事实意味着,如果您使用基于类的代理(proxy-target-class="true")或基于编织的方面(mode="aspectj"),那么代理和编织基础设施无法识别事务设置,并且对象也没有封装在事务代理中。

在代理模式(默认模式)中,只有通过代理传入的外部方法调用被拦截。这意味着自调用(实际上,目标对象中的方法调用目标对象的另一个方法)不会在运行时导致实际的事务,即使调用的方法被标记为@Transactional。此外,代理必须完全初始化才能提供预期的行为,因此您不应该在初始化代码(即@PostConstruct)中依赖这个特性。

如果您希望自调用也被事务包装,那么可以考虑使用AspectJ模式(请参阅下表中的mode属性)。在这种情况下,首先没有代理。相反,目标类被编织(也就是说,它的字节码被修改)以将@Transactional转换为任何类型方法的运行时行为。

基于注解的事务管理配置项

xml中的属性 注解中的属性 Default Description
transaction-manager N/A (see TransactionManagementConfigurerjavadoc) transactionManager 要使用的事务管理器的名称。仅当事务管理器的名称不是transactionManager时才需要,如前面的示例所示。
mode mode proxy 默认模式(proxy)使用Spring的AOP框架处理要代理的带注释的bean(按照前面讨论的代理语义,仅应用于通过代理传入的方法调用)。替代模式(aspectj)将受影响的类与Spring的aspectj事务方面编织在一起,修改目标类字节码以应用于任何类型的方法调用。AspectJ编织需要spring方面。类路径中的jar以及启用加载时编织(或编译时编织)。(有关如何设置加载时编织的详细信息,请参见Spring配置)。
proxy-target-class proxyTargetClass false 仅适用于代理模式。控制为使用@Transactional注释注释的类创建哪种类型的事务代理。如果proxy-target-class属性设置为true,则创建基于类的代理。如果proxy-target-classfalse,或者属性被省略,那么就会创建基于JDK接口的标准代理。(有关不同代理类型的详细研究,请参见代理机制)。
order order Ordered.LOWEST_PRECEDENCE 定义应用于用@Transactional注释的bean的事务通知的顺序。(有关AOP通知排序相关规则的更多信息,请参见通知排序)。没有指定的顺序意味着AOP子系统决定通知的顺序。

处理@Transactional注释的默认通知模式是proxy,它只允许通过代理拦截调用。同一类中的本地调用不能以这种方式被拦截。对于更高级的侦听模式,可以考虑结合编译时或加载时编织切换到aspectj模式。

proxy-target-class属性控制为使用@Transactional注释的类创建哪种类型的事务代理。如果proxy-target-class设置为true,则创建基于类的代理。如果proxy-target-classfalse,或者属性被省略,那么将创建基于JDK接口的标准代理。(有关不同代理类型的讨论,请参阅aop-proxy。)

@EnableTransactionManagement<tx:annotation-driven/>标签仅在定义它们的应用程序上下文中查找@Transactional。这意味着,如果将注释驱动的配置放在DispatcherServletWebApplicationContext中,则它只检查controllers中的@Transactional bean,而不检查services中的@Transactional bean。有关更多信息,请参见MVC

在计算哪个配置对方法上的事务有效时,越接近方法的位置上的配置优先级越高。在下面的示例中,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

  • 事务默认是可读写的。

  • 事务超时默认为基础事务系统的默认超时,如果不支持超时,则为none

  • 任何RuntimeException都会触发回滚,而任何受检的异常都不会触发回滚。

你可以改变这些默认配置。下表总结了@Transactional注释的各种属性:

@Transactional 配置项

属性 类型 Description
value String 可选限定符,指定要使用的事务管理器。
propagation enum: Propagation Optional propagation setting.
isolation enum: Isolation 可选的隔离级别配置。仅适用于REQUIREDREQUIRES_NEW的传播值。
timeout int (单位是:秒) 可选的事务超时。仅适用于REQUIREDREQUIRES_NEW的传播值。
readOnly boolean 读写事务与只读事务。仅适用于REQUIREDREQUIRES_NEW的值。
rollbackFor 异常类的Class对象数组 可选的导致回滚的异常类Class对象数组。
rollbackForClassName 异常类的类名数组 可选的导致回滚的异常类的类名数组。
noRollbackFor 异常类的Class对象数组 可选的不导致回滚的异常类Class对象数组。
noRollbackForClassName 异常类的类名数组 可选的不导致回滚的异常类的类名数组。

目前,您无法显式控制事务的名称,其中“名称”指的是出现在事务监视器(如WebLogic 的事务监视器)和日志输出中的事务名称。对于声明式事务,事务名总是全限定类名+.+被事务通知的方法名。例如,如果BusinessService类的handlePayment(..)方法启动了一个事务,那么事务的名称应该是:com.example.BusinessService.handlePayment

通过@Transactional 配置多个事务管理器

大多数Spring应用程序只需要一个事务管理器,但是在某些情况下,您可能希望在一个应用程序中有多个独立的事务管理器。您可以使用@Transactional注释的value属性来选择性地指定要使用的PlatformTransactionManager的实现类。这可以是bean名,也可以是事务管理器bean的限定符值。例如,使用限定符符号,您可以在应用程序上下文中将以下Java代码与以下事务管理器bean声明组合起来:

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

在前面的示例中,我们使用语法定义了事务管理器限定符,但是我们还可以包括传播行为、回滚规则、超时和其他特性。

1.4.7 事务的传播特性

本节描述Spring中事务传播特性的一些语义。请注意,本节不是事务传播特性的正确介绍。相反,它详细描述了有关Spring中事务传播特性的一些语义。

spring管理的事务中,请注意物理事务和逻辑事务之间的差异,以及传播特性的设置如何应用于这种差异。

理解PROPAGATION_REQUIRED

在这里插入图片描述

PROPAGATION_REQUIRED 强制开启一个物理事务,如果当前作用域内不存在事务则在本地执行该事务,或者参与到外部更大作用域范围的事务中执行。在同一线程内的公共调用堆栈安排中,这是一个很好的默认设置(例如,将服务facade委托给多个存储库方法,其中所有底层资源都必须参与服务级事务)。

默认情况下,参与事务连接外部作用域的特征,静默地忽略本地隔离级别、超时值或只读标志(如果有的话)。如果您希望在使用不同的隔离级别参与现有事务时拒绝隔离级别声明,请考虑将事务管理器上的validateExistingTransactions标志切换为true。这种非宽松模式还拒绝只读不匹配(即试图参与只读外部作用域的内部读写事务)。

当事务的传播特性设置为PROPAGATION_REQUIRED时,将为这个设置应用的每个方法创建一个逻辑事务作用域。每个这样的逻辑事务作用域可以单独确定rollback-only状态,外部事务作用域在逻辑上独立于内部事务作用域。在标准的PROPAGATION_REQUIRED行为的情况下,所有这些作用域都映射到相同的物理事务。因此,在内部事务范围中设置的rollback-only标记确实会影响外部事务实际提交的机会。

但是,在内部事务范围设置rollback-only标记的情况下,外部事务没有决定回滚本身,因此回滚(由内部事务范围静默触发)是意外的。这时会抛出一个对应的UnexpectedRollbackException。这是预期的行为,这样事务的调用者就永远不会被误导,以为提交是在实际上没有执行的情况下执行的。因此,如果一个内部事务(外部调用方不知道该事务)无声地将一个事务标记为rollback-only,那么外部调用方仍然调用commit。外部调用者需要接收一个UnexpectedRollbackException,以清楚地表明执行了回滚。

理解PROPAGATION_REQUIRES_NEW

在这里插入图片描述

PROPAGATION_REQUIRES_NEW,和PROPAGATION_REQUIRED相反,始终为每个受影响的事务作用域使用独立的物理事务,从不参与外部作用域所在的事务。在这种安排中,底层资源事务是不同的,因此可以独立地提交或回滚,外部事务不受内部事务回滚状态的影响,内部事务在完成后立即释放锁。这样一个独立的内部事务还可以声明它自己的隔离级别、超时和只读设置,而不继承外部事务的特征。

理解PROPAGATION_NESTED

PROPAGATION_NESTED使用具有多个保存点的单个物理事务,可以回滚到这些保存点。这种部分回滚允许内部事务作用域触发其作用域的回滚,尽管回滚了一些操作,但外部事务仍然能够继续它的物理事务。该设置通常映射到JDBC保存点,因此它只对JDBC资源事务有效。参见SpringDataSourceTransactionManager

1.4.8 事务操作上的通知

假设您希望同时执行事务操作和一些基本的分析通知。如何在<tx:annotation-driven/>的上下文中实现这一点呢?

当您调用updateFoo(Foo)方法时,您希望看到以下操作:

  • 配置的分析通知启动。
  • 执行事务通知。
  • 执行被通知的对象上的方法。
  • 事务提交。
  • 分析通知报告整个事务方法调用的确切持续时间。

本章不涉及详细解释AOP(除非它应用于事务)。有关AOP配置和一般AOP的详细内容,请参见AOP

下面的代码展示了前面讨论的简单分析切面:

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

    // profile这个方法被定义为一个环绕通知
    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;
    }
}

通知的顺序是通过Ordered接口控制的。有关通知的顺序详细信息,请参见 Advice ordering

下面的配置将创建一个fooService bean,该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">

   <bean id="fooService" class="x.y.service.DefaultFooService"/>

   <!-- 定义的切面 -->
   <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>

您可以以类似的方式配置任意数量的其他切面。

下面的示例创建了与前两个示例相同的设置,但使用的是纯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"/>

    <!-- profiling 通知 -->
    <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>

前面配置的结果是一个fooService bean,它具有按此顺序应用于它的分析和事务方面。如果您希望分析建议在传入的事务建议之后执行,在传出的事务建议之前执行,那么您可以交换分析方面bean的order属性的值,使其高于事务建议的order值。

您可以以类似的方式配置其他方面。

1.4.9 结合AspectJ使用@Transactional

您还可以通过AspectJ 切面在Spring容器之外使用Spring框架的@Transactional支持。为此,首先用@Transactional注释注释类(以及可选的类方法),然后使用在spring-aspects.jar中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect链接(编织)应用程序。您还必须配置事务管理器的切面。您可以使用Spring FrameworkIoC容器来处理依赖注入方面。配置事务管理方面最简单的方法是使用<tx:annotation-driven/>元素,并像使用@Transactional中描述的那样为aspectj指定模式属性。因为我们在这里关注的是在Spring容器之外运行的应用程序,所以我们将向您展示如何通过编程来实现这一点。

在继续之前,您可能希望分别使用@Transactional和AOP进行读取。

下面的例子展示了如何创建一个事务管理器并配置AnnotationTransactionAspect来使用它:

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

当您使用这个切面时,您必须注释实现类(或类中的方法或两者),而不是类实现的接口(如果有的话)。AspectJ遵循Java的规则,不继承接口上的注释。

类上的@Transactional注释指定类中任何公共方法执行的默认事务语义。

类中的方法上的@Transactional注释覆盖类注释(如果存在)给出的默认事务语义。无论可见性如何,您都可以注释任何方法。

要用AnnotationTransactionAspect编织应用程序,必须使用AspectJ构建应用程序(请参阅AspectJ开发指南),或者使用加载时编织。有关使用AspectJ进行加载时编织的讨论,请参见Spring框架中的AspectJ加载时编织

1.5 编程式事务管理

Spring框架提供了两种编程式事务管理方法,通过使用:

  • TransactionTemplate
  • 一个PlatformTransactionManager接口的直接实现类。

Spring团队通常推荐使用TransactionTemplate进行编程式事务管理。第二种方法类似于使用JTA UserTransaction API,尽管异常处理不那么麻烦。

1.5.1 使用TransactionTemplate

TransactionTemplate采用与其他Spring模板(如JdbcTemplate)相同的方法。它使用回调方法(将应用程序代码从获取和释放事务资源的样板中解放出来),并产生意图驱动的代码,因为您的代码只关注您想要做的事情。

如下面的示例所示,使用TransactionTemplate绝对会将您与Spring的事务基础结构和api结合在一起。编程式事务管理是否适合您的开发需求是您必须自己做出的决定。

必须在事务上下文中执行且显式使用TransactionTemplate的应用程序代码类似于下一个示例。作为应用程序开发人员,您可以编写一个TransactionCallback实现(通常表示为匿名内部类),其中包含在事务上下文中需要执行的代码。然后,您可以将自定义TransactionCallback的一个实例传递给TransactionTemplate上公开的execute(..)方法。下面的示例展示了如何这样做:

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

回调中的代码可以通过调用提供的TransactionStatus对象上的setRollbackOnly()方法回滚事务,如下所示:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

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

指定事务配置

您可以在TransactionTemplate上以编程方式或在配置中指定事务设置(例如传播模式、隔离级别、超时等等)。默认情况下,TransactionTemplate实例具有默认的事务设置。下面的示例显示了特定TransactionTemplate的事务性设置的编程自定义:

public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        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:

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

然后,您可以将sharedTransactionTemplate注入所需的任意多个服务中。

最后,TransactionTemplate类的实例是线程安全的,在这种情况下,实例不维护任何会话状态。然而,TransactionTemplate实例可以维护配置状态。因此,虽然许多类可能共享一个TransactionTemplate的单个实例,但是如果一个类需要使用具有不同设置的TransactionTemplate(例如,不同的隔离级别),那么您需要创建两个不同的TransactionTemplate实例。

1.5.2 使用 PlatformTransactionManager

您还可以使用org.springframework.transaction.PlatformTransactionManager直接管理您的事务。为此,通过bean引用将您使用的PlatformTransactionManager的实现传递给您的bean。然后,通过使用TransactionDefinitionTransactionStatus对象,您可以启动事务、回滚和提交。下面的示例展示了如何这样做:

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only 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);

1.6 选择编程式还是声明式事务管理?

**只有在只有少量事务操作时,编程式事务管理才通常是一个好主意。**例如,如果您的web应用程序仅对某些更新操作需要事务,则可能不希望使用Spring或任何其他技术设置事务代理。在这种情况下,使用TransactionTemplate可能是一种很好的方法。能够显式地设置事务名称也只能通过使用事务管理的编程方法来实现。

**另一方面,如果应用程序具有大量事务操作,则声明式事务管理通常是值得的。**它将事务管理排除在业务逻辑之外,并且配置起来并不困难。当使用Spring框架而不是EJB CMT时,声明式事务管理的配置成本会大大降低。

1.7 事务绑定的事件

Spring 4.2开始,事件的侦听器可以绑定到事务的某个阶段。典型的例子是在事务成功完成时处理事件。当当前事务的结果对侦听器很重要时,这样做可以更灵活地使用事件。

您可以使用@EventListener 注解注册一个常规事件侦听器。如果需要将其绑定到事务,请使用@TransactionalEventListener。当您这样做时,默认情况下侦听器被绑定到事务的提交阶段

下一个例子展示了这个概念。假设一个组件发布了一个订单创建的事件,并且我们希望定义一个侦听器,该侦听器只应该在发布该事件的事务成功提交之后处理该事件。下面的示例设置这样的事件侦听器:

@Component
public class MyComponent {

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

@TransactionalEventListener 注解公开了一个phase属性,该属性允许您自定义应该将侦听器绑定到的事务的阶段。有效的阶段包括BEFORE_COMMITAFTER_COMMIT(默认)、AFTER_ROLLBACKAFTER_COMPLETION,它们聚合事务完成(无论是提交还是回滚)。

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

1.8 应用服务器的集成

Spring的事务抽象通常与应用服务器无关。此外,SpringJtaTransactionManager类(它可以选择性地为JTA UserTransactionTransactionManager对象执行JNDI查找)自动检测后一个对象的位置,后者的位置因应用服务器的不同而不同。访问JTA TransactionManager允许增强事务语义,特别是支持事务挂起。有关详细信息,请参见JtaTransactionManager javadoc。

SpringJtaTransactionManager是在Java EE应用服务器上运行的标准选择,众所周知,它可以在所有公共服务器上运行。高级功能(如事务挂起)也可以在许多服务器上工作(包括GlassFishJBossGeronimo),而不需要任何特殊配置。但是,对于完全支持的事务挂起和进一步的高级集成,Spring包含针对WebLogic ServerWebSphere的特殊适配器。下面几节将讨论这些适配器。

对于标准场景,包括WebLogic ServerWebSphere,可以考虑使用方便的<tx:jta-transaction-manager/>配置元素。配置后,此元素自动检测底层服务器并选择平台可用的最佳事务管理器。这意味着您不需要显式地配置特定于服务器的适配器类(如下面的部分所述)。相反,它们是自动选择的,标准的JtaTransactionManager是默认的回退方法。

1.8.1 IBM WebSphere

WebSphere 6.1.0.9及以上版本中,推荐使用的Spring JTA事务管理器是WebSphereUowTransactionManager。这个特殊适配器使用IBMUOWManager API,该APIWebSphere Application Server 6.1.0.9及更高版本中可用。使用此适配器,IBM正式支持Spring驱动的事务挂起(由PROPAGATION_REQUIRES_NEW发起的挂起和恢复)。

1.8.2 Oracle WebLogic Server

WebLogic Server 9.0或更高版本上,您通常使用WebLogicJtaTransactionManager而不是stock JtaTransactionManager类。这个特殊的特定于weblogic的普通JtaTransactionManager子类支持weblogic管理的事务环境中Spring事务定义的全部功能,超出了标准的JTA语义。特性包括事务名称、每个事务隔离级别以及在所有情况下正确恢复事务。

1.9 常见问题的解决方法

本节描述一些常见问题的解决方案。

1.9.1 对特定的DataSource使用了错误的事务管理

根据您对事务技术和需求的选择,使用正确的PlatformTransactionManager实现。如果使用得当,Spring框架仅仅提供了一个简单而可移植的抽象。如果使用全局事务,则必须使用org.springframework.transaction.jta.JtaTransactionManager类(或其特定于应用程序服务器的子类)。否则,事务基础结构将尝试在容器数据源实例等资源上执行本地事务。这样的本地事务没有意义,好的应用服务器会将它们视为错误。

1.10 其他资源

有关Spring框架事务支持的更多信息,请参见:

  • Spring中的分布式事务,有XA和没有XA。是一个JavaWorld表示,其中SpringDavid Syer指导您了解Spring应用程序中分布式事务的7种模式,其中3种带有XA, 4种没有。
  • Java事务设计策略 InfoQ提供了一本书,对Java事务进行了快速的介绍。它还包括如何使用Spring框架和EJB3配置和使用事务的并列示例。

猜你喜欢

转载自blog.csdn.net/hbtj_1216/article/details/86666359