1.事务管理

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

  • 跨不同事务API的一致编程模型,例如Java Transaction API(JTA),JDBC,Hibernate和Java Persistence API(JPA)。

  • 支持声明式事务管理。

  • 比复杂事务API(如JTA)更简单的编程事务管理API。

  • 与Spring的数据访问抽象集成。

以下部分描述了Spring Framework的事务功能和技术:

  • Spring Framework的事务支持模型的优点描述了为什么要使用Spring Framework的事务抽象而不是EJB容器管理事务(CMT)或选择通过专有API(如Hibernate)来驱动本地事务。

  • 理解Spring Framework事务抽象概述了核心类,并描述了如何从各种源配置和获取DataSource实例。

  • 将资源与事务同步描述了应用程序代码如何确保正确创建,重用和清理资源。

  • 声明式事务管理描述了对声明式事务管理的支持。

  • 程序化事务管理包括对程序化(即明确编码)事务管理的支持。

  • 事务绑定事件描述了如何在事务中使用应用程序事件。

本章还包括对最佳实践,应用程序服务器集成以及常见问题解决方案的讨论。

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

传统上,Java EE开发人员有两种事务管理选择:全局或本地事务,这两种事务都有很大的局限性。在接下来的两节中将对全局和本地事务管理进行审查,然后讨论Spring Framework的事务管理支持如何解决全局和本地事务模型的局限性。

1.1.1 全局事务

全局事务允许你使用多个事务资源,通常是关系数据库和消息队列。应用程序服务器通过JTA管理全局事务,这是一个繁琐的API(部分原因是它的异常模型)。此外,JTA UserTransaction通常需要从JNDI获取,这意味着你还需要使用JNDI才能使用JTA。全局事务的使用限制了应用程序代码的任何潜在重用,因为JTA通常仅在应用程序服务器环境中可用。

以前,使用全局事务的首选方法是通过EJB CMT(容器管理事务)。 CMT是一种声明式事务管理(与程序化事务管理不同)。 EJB CMT消除了与事务相关的JNDI查找的需要,尽管EJB本身的使用需要使用JNDI。它消除了编写Java代码以控制事务的大部分但不是全部的需要。重要的缺点是CMT与JTA和应用服务器环境相关联。此外,仅当选择在EJB中实现业务逻辑(或至少在事务EJB外观之后)时,它才可用。一般来说,EJB的负面影响是如此之大,以至于这不是一个有吸引力的主张,特别是面对声明式事务管理的令人信服的替代方案。

1.1.2 本地交易

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

1.1.3 Spring Framework的一致性编程模型

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

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

你是否需要应用程序服务器进行事务管理?

Spring Framework的事务管理支持改变了关于企业Java应用程序何时需要应用程序服务器的传统规则。

特别是,你不需要纯粹用于通过EJB进行声明式事务的应用程序服务器。实际上,即使你的应用程序服务器具有强大的JTA功能,你也可以决定Spring Framework的声明式事务提供比EJB CMT更强大的功能和更高效的编程模型。

通常,只有当你的应用程序需要处理跨多个资源的事务时,才需要应用程序服务器的JTA功能,这对许多应用程序来说并不是必需的。许多高端应用程序使用单个高度可伸缩的数据库(例如Oracle RAC)。独立的事务管理器(例如Atomikos Transactions和JOTM)是其他选项。当然,你可能需要其他应用程序服务器功能,例如Java消息服务(JMS)和Java EE连接器体系结构(JCA)。

Spring Framework使你可以选择何时将应用程序扩展到完全加载的应用程序服务器。使用EJB CMT或JTA的唯一替代方法是使用本地事务(例如JDBC连接上的代码)编写代码,并且如果你需要在全局容器管理的事务中运行代码,则会面临大量的返工。使用Spring Framework,只需要更改配置文件中的一些bean定义(而不是代码)。

1.2 理解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容器中的任何其他对象(或bean)相同。仅使用此优势使Spring Framework事务成为一种有价值的抽象,即使你使用JTA也是如此。与直接使用JTA相比,你可以更轻松地测试事务代码。

同样,为了与Spring的理念保持一致,可以取消选中任何PlatformTransactionManager接口的方法抛出的TransactionException(即,它扩展了java.lang.RuntimeException类)。交易基础设施故障几乎总是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获并处理TransactionException。重点是开发人员不会被迫这样做。

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

TransactionDefinition接口指定:

  • Propagation:通常,在事务范围内执行的所有代码都在该事务中运行。但是,如果在事务上下文已存在时执行事务方法,则可以指定行为。例如,代码可以继续在现有事务中运行(常见情况),或者可以暂停现有事务并创建新事务。 Spring提供了EJB CMT中熟悉的所有事务传播选项。要阅读有关Spring中事务传播的语义,请参阅事务传播。

  • Isolation:此事务与其他事务的工作隔离的程度。例如,此事务是否可以看到来自其他事务的未提交的写入?

  • Timeout:此事务在超时之前运行多长时间并由底层事务基础结构自动回滚。

  • Read-only status:你可以在代码读取时使用只读事务,但不能修改数据。在某些情况下,只读事务可能是一种有用的优化,例如当你使用Hibernate时。

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

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

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

你可以通过创建类似于以下内容的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获得的容器DataSource以及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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        https://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(或任何其他特定资源),因为它使用容器的全局事务管理基础结构。

dataSource bean的前面定义使用jee名称空间中的<jndi-lookup />标记。有关更多信息,请参阅JEE架构。

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

DataSource bean定义类似于前面显示的本地JDBC示例,因此未在以下示例中显示。

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

在这种情况下,txManager bean是HibernateTransactionManager类型。与DataSourceTransactionManager需要对DataSource的引用一样,HibernateTransactionManager需要对SessionFactory的引用。以下示例声明了sessionFactory和txManager 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>

如果使用Hibernate和Java EE容器管理的JTA事务,则应使用与之前的JDBC JTA示例相同的JtaTransactionManager,如以下示例所示:

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

如果使用JTA,则无论使用何种数据访问技术,无论是JDBC,Hibernate JPA还是任何其他支持的技术,你的事务管理器定义应该看起来都一样。 这是因为JTA事务是全局事务,可以登记任何事务资源。

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

1.3 资源与事务同步

现在应该清楚如何创建不同的事务管理器以及它们如何链接到需要与事务同步的相关资源(例如,将DataSourceTransactionManager连接到JDBC数据源,HibernateTransactionManager连接到Hibernate SessionFactory等等)。本节描述应用程序代码(直接或间接使用诸如JDBC,Hibernate或JPA之类的持久性API)如何确保正确创建,重用和清理这些资源。本节还讨论了如何(可选)通过相关的PlatformTransactionManager触发事务同步。

1.3.1 高级同步方法

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

1.3.2。低级同步方法 诸如DataSourceUtils(用于JDBC),EntityManagerFactoryUtils(用于JPA),SessionFactoryUtils(用于Hibernate)等的类存在于较低级别。当你希望应用程序代码直接处理本机持久性API的资源类型时,你可以使用这些类来确保获得正确的Spring Framework托管实例,(可选)同步事务,并且在此过程中发生的异常是正确映射到一致的API。

例如,在JDBC的情况下,你可以改为使用Spring的org.springframework.jdbc.datasource.DataSourceUtils类,而不是传统的JDBC方法来调用DataSource上的getConnection()方法,如下所示:

Connection conn = DataSourceUtils.getConnection(dataSource);

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

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

当然,一旦你使用了Spring的JDBC支持,JPA支持或Hibernate支持,你通常不希望使用DataSourceUtils或其他帮助程序类,因为你通过Spring抽象工作比直接使用相关API更快乐。例如,如果你使用Spring JdbcTemplate或jdbc.object包来简化JDBC的使用,则在幕后进行正确的连接检索,你无需编写任何特殊代码。

1.3.3 TransactionAwareDataSourceProxy

在最低级别存在TransactionAwareDataSourceProxy类。这是目标DataSource的代理,它包装目标DataSource以添加对Spring管理的事务的感知。在这方面,它类似于Java EE服务器提供的事务性JNDI数据源。

你几乎从不需要或不想使用此类,除非必须调用现有代码并传递标准JDBC DataSource接口实现。在这种情况下,此代码可能可用但参与Spring管理的事务。你可以使用前面提到的更高级别的抽象来编写新代码。

1.4 声明式事务管理

大多数Spring Framework用户选择声明式事务管理。此选项对应用程序代码的影响最小,因此与非侵入式轻量级容器的理想最为一致。 使用Spring面向方面编程(AOP),Spring Framework的声明式事务管理成为可能。但是,由于事务方面代码随Spring Framework发行版一起提供并且可能以样板方式使用,因此通常不必理解AOP概念以有效使用此代码。

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

与绑定到JTA的EJB CMT不同,Spring Framework的声明式事务管理适用于任何环境。它可以通过调整配置文件使用JDBC,JPA或Hibernate来处理JTA事务或本地事务。

你可以将Spring Framework声明式事务管理应用于任何类,而不仅仅是EJB等特殊类。

Spring Framework提供了声明性回滚规则,这是一个没有EJB等价的功能。提供了对回滚规则的编程和声明性支持。

Spring Framework允许你使用AOP自定义事务行为。例如,你可以在事务回滚的情况下插入自定义行为。你还可以添加任意建议以及事务建议。使用EJB CMT,除了使用setRollbackOnly()之外,你无法影响容器的事务管理。

Spring框架不支持跨远程调用传播事务上下文,就像高端应用程序服务器那样。如果你需要此功能,我们建议你使用EJB。但是,在使用此类功能之前请仔细考虑,因为通常情况下,人们不希望事务跨越远程调用。

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

虽然EJB容器默认行为会自动回滚系统异常(通常是运行时异常)上的事务,但EJB CMT不会在应用程序异常(即java.rmi.RemoteException以外的已检查异常)上自动回滚事务。虽然声明式事务管理的Spring默认行为遵循EJB约定(回滚仅在未经检查的异常上自动执行),但定制此行为通常很有用。

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

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

关于Spring Framework的声明式事务支持,最重要的概念是通过AOP代理启用此支持,并且事务性建议由元数据(当前基于XML或基于注解)驱动。 AOP与事务元数据的组合产生一个AOP代理,该代理使用TransactionInterceptor和适当的PlatformTransactionManager实现来驱动围绕方法调用的事务。

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

1.4.2 声明性事务实现的示例

请考虑以下接口及其附带实现。 此示例使用Foo和Bar类作为占位符,以便你可以专注于事务使用而无需关注特定的域模型。 出于此示例的目的,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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 bean,transactional。 要应用的事务语义封装在<tx:advice />定义中。 <tx:advice />定义读作“所有方法,从get开始,将在只读事务的上下文中执行,所有其他方法将使用默认事务语义执行”。 <tx:advice />标记的transaction-manager属性设置为将驱动事务的PlatformTransactionManager bean的名称(在本例中为txManager bean)。

如果要连接的PlatformTransactionManager的bean名称具有名称transactionManager,则可以省略事务建议(<tx:advice />)中的transaction-manager属性。 如果要连接的PlatformTransactionManager bean具有任何其他名称,则必须显式使用transaction-manager属性,如上例所示。

<aop:config />定义确保txAdvice bean定义的事务性建议在程序中的适当位置执行。 首先,定义一个切入点,该切入点与FooService接口(fooServiceOperation)中定义的任何操作的执行相匹配。 然后使用顾问程序将切入点与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>

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

前面显示的配置用于围绕从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());
    }
}

运行前面的程序的输出应类似于以下内容(为清楚起见,LogFJ输出和DefaultFooService类的insertFoo(..)方法抛出的UnsupportedOperationException中的堆栈跟踪已被截断):

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

1.4.3 回滚声明性事务

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

向Spring Framework的事务基础结构指示事务的工作将被回滚的推荐方法是从当前在事务上下文中执行的代码中抛出异常。 Spring Framework的事务基础结构代码捕获任何未处理的异常,因为它冒泡调用堆栈并确定是否将事务标记为回滚。

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

你可以准确配置哪些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 {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

如果可能的话,强烈建议你使用声明性方法进行回滚。 如果你绝对需要程序化回滚,则可以使用程序化回滚,但它的使用方式可以实现基于POJO的简洁体系结构。

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

考虑具有多个服务层对象的情况,并且你希望对每个对象应用完全不同的事务配置。 你可以通过使用不同的切入点和advice-ref属性值定义不同的<aop:advisor />元素来实现。

作为比较,首先假设你的所有服务层类都在根x.y.service包中定义。 要使所有作为在该包(或子包中)中定义的类的实例的bean以及以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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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>

以下示例显示如何使用完全不同的事务设置配置两个不同的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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>

1.4.5 <tx:advice/>设置

本节总结了你可以使用<tx:advice />标记指定的各种事务设置。 默认的<tx:advice />设置为:

  • 传播设置是必需的。

  • 隔离级别为DEFAULT。

  • 该事务是读写的。

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

  • 任何RuntimeException都会触发回滚,而任何已检查的Exception都不会。

你可以更改这些默认设置。 下表总结了嵌套在<tx:advice/>和<tx:attributes />标记内的<tx:method />标记的各种属性:

属性是否必须?默认值描述

name

Yes

与事务属性关联的方法名称。 通配符()字符可用于将相同的事务属性设置与多个方法相关联(例如,get ,handle ,on Event等)。

propagation

No

REQUIRED

事务传播行为。

isolation

No

DEFAULT

事务隔离级别。 仅适用于REQUIRED或REQUIRES_NEW的传播设置。

timeout

No

-1

事务超时(秒)。 仅适用于传播REQUIRED或REQUIRES_NEW。

read-only

No

false

读写与只读事务。 仅适用于REQUIRED或REQUIRES_NEW。

rollback-for

No

以逗号分隔的触发回滚的异常实例列表。 例如,com.foo.MyBusinessException,ServletException。

no-rollback-for

No

以逗号分隔的不触发回滚的异常实例列表。 例如,com.foo.MyBusinessException,ServletException。

1.4.6 使用@Transactional

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

标准的javax.transaction.Transactional注解也被支持作为Spring自己的注解的替代品。 有关更多详细信息,请参阅JTA 1.2文档。

使用@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上下文中定义为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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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属性,如上例所示。

方法可见性和@Transactional

使用代理时,应仅将@Transactional注解应用于具有公共可见性的方法。如果使用@Transactional注解对带保护的,私有的或包可见的方法进行注解,则不会引发错误,但带注解的方法不会显示已配置的事务设置。如果需要注解非公共方法,请考虑使用AspectJ(稍后介绍)。

你可以将@Transactional注解应用于接口定义,接口上的方法,类定义或类上的公共方法。但是,仅仅存在@Transactional注解不足以激活事务行为。 @Transactional注解仅仅是某些运行时基础结构可以使用的元数据,这些运行时基础结构是@Transactional-aware,可以使用元数据来配置具有事务行为的适当bean。在前面的示例中,<tx:annotation-driven />元素打开事务行为。

Spring团队建议你使用@Transactional注解仅注解具体类(以及具体类的方法),而不是注解接口。你当然可以将@Transactional注解放在接口(或接口方法)上,但这只能在你使用基于接口的代理时按预期工作。 Java注解不是从接口继承的事实意味着,如果使用基于类的代理(proxy-target-class =“true”)或基于编织的方面(mode =“aspectj”),则事务设置不是由代理和编织基础结构识别,并且对象不包含在事务代理中。

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

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

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

transaction-manager

N/A(参见TransactionManagementConfigurer javadoc)

transactionManager

要使用的事务管理器的名称。仅当事务管理器的名称不是transactionManager时才需要,如前面的示例所示。

mode

mode

proxy

默认模式(代理)使用Spring的AOP框架处理要注解的注解bean(遵循代理语义,如前所述,仅适用于通过代理进入的方法调用)。替代模式(aspectj)使用Spring的AspectJ事务方面​​编译受影响的类,修改目标类字节代码以应用于任何类型的方法调用。 AspectJ编织需要类路径中的spring-aspects.jar以及启用加载时编织(或编译时编织)。 (有关如何设置加载时编织的详细信息,请参阅Spring配置。)

proxy-target-class

proxyTargetClass

false

仅适用于代理模式。控制为使用@Transactional注解注解的类创建的事务代理类型。如果proxy-target-class属性设置为true,则创建基于类的代理。如果proxy-target-class为false或者省略了该属性,则会创建基于标准JDK接口的代理。 (有关不同代理类型的详细检查,请参阅代理机制。)

order

order

Ordered.LOWEST_PRECEDENCE

定义应用于使用@Transactional注解的bean的事务通知的顺序。 (有关与AOP建议排序相关的规则的更多信息,请参阅建议排序。)没有指定的排序意味着AOP子系统确定建议的顺序。

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

proxy-target-class属性控制为使用@Transactional注解注解的类创建的事务代理类型。如果proxy-target-class设置为true,则创建基于类的代理。如果proxy-target-class为false或者省略了该属性,则会创建基于标准JDK接口的代理。 (有关不同代理类型的讨论,请参阅[aop-proxying]。)

@EnableTransactionManagement和<tx:annotation-driven />仅在定义它们的同一应用程序上下文中的bean上查找@Transactional。这意味着,如果你将注解驱动的配置放在DispatcherServlet的WebApplicationContext中,它只会在你的控制器而不是你的服务中检查@Transactional bean。有关更多信息,请参阅MVC。

在评估方法的事务设置时,派生最多的位置优先。在以下示例的情况下,DefaultFooService类在类级别使用只读事务的设置进行注解,但同一类中updateFoo(Foo)方法的@Transactional注解优先于定义的事务设置在class级别。

@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设置如下:

  • 传播设置为PROPAGATION_REQUIRED。

  • 隔离级别为ISOLATION_DEFAULT。

  • 该事务是读写的。

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

  • 任何RuntimeException都会触发回滚,而任何已检查的Exception都不会。

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

PropertyTypeDescription

value

String

可选限定符,指定要使用的事务管理器。

propagation

enum: Propagation

可选的传播设置。

isolation

enum: Isolation

可选的隔离级别。 仅适用于REQUIRED或REQUIRES_NEW的传播值。

timeout

int(以秒为单位)

可选的事务超时。 仅适用于REQUIRED或REQUIRES_NEW的传播值。

readOnly

boolean

读写与只读事务。 仅适用于REQUIRED或REQUIRES_NEW的值。

rollbackFor

Class对象的数组,必须从Throwable派生。

必须引发回滚的可选异常类数组。

rollbackForClassName

类名数组。 这些类必须从Throwable派生。

必须导致回滚的可选异常类名称数组。

noRollbackFor

Class对象的数组,必须从Throwable派生。

可选的异常类数组,不得导致回滚。

noRollbackForClassName

String类名称的数组,必须从Throwable派生。

可选不会导致回滚的异常类名称的可选数组。

目前,你无法明确控制事务的名称,其中“name”表示事务监视器中显示的事务名称(如果适用)(例如,WebLogic的事务监视器)和日志记录输出。对于声明性事务,事务名称始终是完全限定的类名+。 +事务建议类的方法名称。例如,如果BusinessService类的handlePayment(..)方法启动了事务,则事务的名称将为:com.example.BusinessService.handlePayment。

使用@Transactional的多个Transaction Managers

大多数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上的两个方法在单独的事务管理器下运行,由顺序和帐户限定符区分。 如果未找到特定限定的PlatformTransactionManager bean,则仍会使用默认的<tx:annotation-driven>目标bean名称transactionManager。

自定义快捷方式注解

如果你发现在许多不同方法上重复使用与@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在当前范围内(如果尚未存在事务)或参与为更大范围定义的现有“外部”事务,则在本地执行物理事务。这是同一线程中常见调用堆栈安排的一个很好的默认值(例如,委托给几个存储库方法的服务外观,其中所有底层资源都必须参与服务级别事务)。

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

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

但是,在内部事务作用域设置仅回滚标记的情况下,外部事务尚未决定回滚本身,因此回滚(由内部事务作用域静默触发)是意外的。此时抛出相应的UnexpectedRollbackException。这是预期的行为,因此事务的调用者永远不会被误导以假定在实际上没有执行提交。因此,如果内部事务(外部调用者不知道)默认将事务标记为仅回滚,则外部调用者仍调用commit。外部调用者需要接收UnexpectedRollbackException以清楚地指示已执行回滚。

了解PROPAGATION_REQUIRES_NEW

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

了解PROPAGATION_NESTED

PROPAGATION_NESTED使用具有多个保存点的单个物理事务,它可以回滚到该事务。这种部分回滚允许内部事务作用域触发其作用域的回滚,外部事务能够继续物理事务,尽管已经回滚了一些操作。此设置通常映射到JDBC保存点,因此它仅适用于JDBC资源事务。请参阅Spring的DataSourceTransactionManager。

1.4.8 为交易操作提供Advising

假设你要执行事务操作和一些基本概要分析建议。 你如何在<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;

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

通过Ordered接口控制建议的顺序。 有关建议订购的完整详细信息,请参阅建议订购。

以下配置创建一个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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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>

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

以下示例创建与前两个示例相同的设置,但使用纯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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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>

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

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

1.4.9 将@Transactional与AspectJ一起使用

你还可以通过AspectJ方面在Spring容器外部使用Spring Framework的@Transactional支持。为此,首先使用@Transactional注解注解你的类(以及可选的类的方法),然后使用spring-aspects.jar文件中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect链接(编织)你的应用程序。 。你还必须使用事务管理器配置方面。你可以使用Spring Framework的IoC容器来处理依赖注入方面。配置事务管理方面的最简单方法是使用<tx:annotation-driven />元素并将aspect属性指定为aspectj,如使用@Transactional中所述。因为我们专注于在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 Framework中使用AspectJ进行加载时编织。

1.5 程序化交易管理

Spring Framework提供了两种程序化事务管理方法,使用:

  • 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。 然后,通过使用TransactionDefinition和TransactionStatus对象,你可以启动事务,回滚和提交。 以下示例显示了如何执行此操作:

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 Framework而不是EJB CMT时,声明式事务管理的配置成本大大降低。

1.7 交易约束事件

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

你可以使用@EventListener注解注册常规事件侦听器。如果需要将其绑定到事务,请使用@TransactionalEventListener。执行此操作时,默认情况下,侦听器将绑定到事务的提交阶段。

下一个例子显示了这个概念。假设一个组件发布一个订单创建的事件,并且我们想要定义一个只应该在发布它的事务成功提交后才应该处理该事件的监听器。以下示例设置此类事件侦听器:

@Component
public class MyComponent {

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

@TransactionalEventListener注解公开了一个阶段属性,该属性允许你自定义侦听器应绑定到的事务的阶段。有效阶段是BEFORE_COMMIT,AFTER_COMMIT(默认值),AFTER_ROLLBACK和AFTER_COMPLETION,它们聚合事务完成(无论是提交还是回滚)。

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

1.8 特定于应用程序服务器的集

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

Spring的JtaTransactionManager是在Java EE应用程序服务器上运行的标准选择,并且已知可在所有常见服务器上运行。高级功能(例如事务暂停)也适用于许多服务器(包括GlassFish,JBoss和Geronimo),无需任何特殊配置。但是,对于完全支持的事务挂起和进一步的高级集成,Spring包含用于WebLogic Server和WebSphere的特殊适配器。以下各节将讨论这些适配器。

对于标准方案(包括WebLogic Server和WebSphere),请考虑使用方便的<tx:jta-transaction-manager />配置元素。配置后,此元素会自动检测基础服务器并选择可用于平台的最佳事务管理器。这意味着你无需显式配置特定于服务器的适配器类(如以下部分所述)。相反,它们是自动选择的,标准的JtaTransactionManager是默认的后备。

1.8.1 IBM WebSphere

在WebSphere 6.1.0.9及更高版本中,要使用的推荐Spring JTA事务管理器是WebSphereUowTransactionManager。此特殊适配器使用IBM的UOWManager API,该API在WebSphere Application Server 6.1.0.9及更高版本中可用。使用此适配器,IBM正式支持Spring驱动的事务暂停(由PROPAGATION_REQUIRES_NEW启动的暂停和恢复)。

1.8.2 Oracle WebLogic Server

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

1.9 解决常见问题

本节介绍一些常见问题的解决方案。

1.9.1 使用错误的事务管理器获取特定的数据源

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

1.10 更多资源

有关Spring Framework的事务支持的更多信息,请参阅:

  • Spring中的分布式事务,有和没有XA是一个JavaWorld演示文稿,其中Spring的David Syer引导你完成Spring应用程序中分布式事务的七种模式,其中三种模式使用XA,另外四种没有。

  • Java Transaction Design Strategies是InfoQ提供的一本书,它提供了对Java中事务的快速介绍。它还包括如何使用Spring Framework和EJB3配置和使用事务的并排示例。

最后更新于