17. Transaction 管理

17.1 Spring Framework事务管理简介

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

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

  • 支持 declarative transaction management

  • 用于 programmatic 事务管理的简单API比复杂的事务API(如JTA)更简单。

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

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

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

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

17.2.1 全球 Transaction

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

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

17.2.2 本地 Transaction

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

17.2.3 Spring Framework的一致编程模型

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

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


Do you need an application server for transaction management?

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

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

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

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


17.3 了解Spring Framework事务抽象

Spring事务抽象的关键是事务策略的概念。事务策略由 org.springframework.transaction.PlatformTransactionManager 接口定义:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

这主要是服务提供者接口(SPI),尽管它可以从您的应用程序代码中使用 programmatically 。因为 PlatformTransactionManager 是一个接口,所以可以根据需要轻松地模拟或存根。它与诸如JNDI之类的查找策略无关。 PlatformTransactionManager 实现的定义与Spring Framework IoC容器中的任何其他对象(或bean)相同。仅使用此优势,即使您使用JTA,Spring Framework事务也是值得抽象的。与直接使用JTA相比,可以更轻松地测试事务代码。

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

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

TransactionDefinition 接口指定:

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

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

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

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

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

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

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

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

PlatformTransactionManager 实现通常需要了解它们工作的环境:JDBC,JTA,Hibernate等。以下示例显示了如何定义本地 PlatformTransactionManager 实现。 (此示例适用于普通JDBC。)

你定义了一个JDBC DataSource

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

然后,相关的 PlatformTransactionManager 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
        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 或任何其他特定资源,因为它使用容器的全局事务管理基础结构。

dataSource bean的上述定义使用jee名称空间中的标记。有关基于模式的配置的更多信息,请参见第41章基于XML模式的配置,有关标记的更多信息,请参阅第41.2.3节“jee模式”一节。

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

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

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

在这种情况下, txManager bean属于 HibernateTransactionManager 类型。与 DataSourceTransactionManager 需要对 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事务,那么您应该使用与之前的JDBC JTA示例相同的 JtaTransactionManager

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

如果您使用JTA,那么无论您使用什么数据访问技术,无论是JDBC,Hibernate JPA还是任何其他支持的技术,您的事务管理器定义都将看起来相同。这是因为JTA事务是全局事务,可以登记任何事务资源。

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

17.4 将资源与事务同步

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

17.4.1 高级同步方法

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

17.4.2 低级同步方法

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

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

Connection conn = DataSourceUtils.getConnection(dataSource);

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

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

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

17.4.3 TransactionAwareDataSourceProxy

在最低级别存在 TransactionAwareDataSourceProxy 类。这是目标 DataSource 的代理,它包装目标 DataSource 以增加对Spring管理的事务的认识。在这方面,它类似于Java EE服务器提供的事务JNDI DataSource

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

17.5 声明式事务管理

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

使用Spring面向方面编程(AOP)可以实现Spring Framework的声明式事务管理,但是,由于事务方面代码随Spring Framework分发一起提供并且可能以样板方式使用,因此通常不必理解AOP概念有效使用此代码。

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

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

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

  • Spring Framework提供了声明性的 rollback rules, 功能,没有EJB等价物。提供了对回滚规则的编程和声明性支持。

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

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


Where is TransactionProxyFactoryBean?

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

Spring 2.0之前的配置样式仍然是100%有效的配置;认为新的 <tx:tags/> 只是简单地定义你的 TransactionProxyFactoryBean beans 代表。


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

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

17.5.1 了解Spring Framework的声明式事务实现

仅仅通过 @Transactional 注释对您的类进行注释,将 @EnableTransactionManagement 添加到您的配置中,然后期望您了解它是如何工作的,这是不够的。本节介绍了在发生与事务相关的问题时Spring Framework的声明式事务基础结构的内部工作原理。

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

Spring AOP在第11章“面向方面的Spring编程”中介绍。

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

tx

17.5.2 声明式事务实现的示例

请考虑以下接口及其附带实现。此示例使用 FooBar 类作为占位符,以便您可以专注于事务使用而无需关注特定的域模型。出于本示例的目的, DefaultFooService 类在每个实现的方法的主体中抛出 UnsupportedOperationException 实例的事实是好的;它允许您查看创建的事务,然后回滚以响应 UnsupportedOperationException 实例。

// 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) 必须在具有只读语义的事务的上下文中执行,并且其他方法 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">

    <!-- 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中切入点表达式的更多详细信息,请参阅 Chapter 11, Aspect Oriented Programming with Spring

常见的要求是使整个服务层具有事务性。执行此操作的最佳方法是更改切入点表达式以匹配服务层中的任何操作。例如:

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

在此示例中,假设您的所有服务接口都在x.y.service包中定义;见第11章,使用Spring进行面向方面编程以获取更多详细信息

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

上面的配置将用于创建围绕从 fooService bean定义创建的对象的事务代理。代理将使用事务性建议进行配置,以便在代理上调用适当的方法时,将启动,挂起事务,标记为只读,依此类推,具体取决于与该方法关联的事务配置。考虑以下测试驱动上述配置的程序:

public final class Boot {

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

运行前面程序的输出将类似于以下内容。 (为清楚起见,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 [[email protected]] 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 [[email protected]]
[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)

17.5.3 回滚声明性事务

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

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

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

您可以准确配置哪些 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的简洁体系结构。

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

考虑具有多个服务层对象的情况,并且您希望对每个对象应用完全不同的事务配置。您可以通过使用不同的 pointcutadvice-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
        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>

以下示例显示如何使用完全不同的事务设置配置两个不同的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>

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

17.5.5 <tx:advice />设置

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

  • Propagation settingREQUIRED.

  • 隔离级别为 DEFAULT.

  • 事务是读/写。

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

  • 任何 RuntimeException 触发回滚,任何已检查的 Exception 都没有。

您可以更改这些默认设置;嵌套在 <tx:advice/><tx:attributes/> 标签中的 <tx:method/> 标签的各种属性总结如下:

Table 17.1. tx:method/ settings

属性必填?默认说明
name与事务属性关联的方法名称。通配符(*)字符可用于将相同的事务属性设置与多个方法相关联;例如, get*handle*on*Event 等。
propagationREQUIRED事务传播行为。
isolationDEFAULT事务隔离级别。仅适用于传播REQUIRED或REQUIRES_NEW。
timeout-1事务超时(秒)。仅适用于传播REQUIRED或REQUIRES_NEW。
read-onlyfalse读/写与只读事务。仅适用于REQUIRED或REQUIRES_NEW。
rollback-forException(s) 触发回滚;逗号分隔。例如, com.foo.MyBusinessException,ServletException.
no-rollback-forException(s) 不触发回滚;逗号分隔。例如, com.foo.MyBusinessException,ServletException.

17.5.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 IoC容器中的bean时,可以通过仅添加一行XML配置来使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。


Method visibility and @Transactional

使用代理时,应仅将 @Transactional 注释应用于具有公共可见性的方法。如果使用 @Transactional 注释注释protected,private或package-visible方法,则不会引发错误,但带注释的方法不会显示已配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(见下文)。


您可以在接口定义,接口上的方法,类定义或类的公共方法之前放置 @Transactional 注释。但是,仅仅存在 @Transactional 注释不足以激活事务行为。 @Transactional 注释只是元数据,可以由某些运行时基础结构使用,该运行时基础结构可以使用元数据来配置具有事务行为的适当Bean。在前面的示例中, <tx:annotation-driven/> 元素会切换事务行为。

Spring建议您仅使用@Transactional注释来注释具体类(以及具体类的方法),而不是注释接口。您当然可以将@Transactional注释放在接口(或接口方法)上,但这只能在您使用基于接口的代理时按预期工作。 Java注释不是从接口继承的事实意味着如果您使用基于类的代理(proxy-target-class =“true”)或基于编织的方面(mode =“aspectj”),那么事务设置是代理和编织基础设施无法识别,并且该对象不会被包装在事务代理中,这将是非常糟糕的。

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

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

Table 17.2. Annotation driven transaction settings

XML属性注释属性默认说明
transaction-manager不适用(请参阅 TransactionManagementConfigurer javadocs)transactionManager要使用的事务管理器的名称。仅在事务管理器的名称不是 transactionManager 时才需要,如上例所示。
modemodeproxy默认模式 "proxy" 使用Spring的AOP框架处理要注释的带注释的bean(遵循代理语义,如上所述,仅适用于通过代理进入的方法调用)。替代模式 "aspectj" 使用Spring的AspectJ事务方面编织受影响的类,修改目标类字节代码以应用于任何类型的方法调用。 AspectJ编织需要在类路径中使用spring-aspects.jar以及启用加载时编织(或编译时编织)。 (有关如何设置加载时编织的详细信息,请参阅 the section called “Spring configuration” 。)
proxy-target-classproxyTargetClassfalse仅适用于代理模式。控制为使用 @Transactional 注释注释的类创建的事务代理类型。如果 proxy-target-class 属性设置为 true ,则会创建基于类的代理。如果 proxy-target-classfalse 或者省略了该属性,则会创建基于标准JDK接口的代理。 (有关不同代理类型的详细检查,请参阅 Section 11.6, “Proxying mechanisms” 。)
orderorderOrdered.LOWEST_PRECEDENCE定义应用于使用 @Transactional 注释的bean的事务通知的顺序。 (有关与AOP建议排序相关的规则的更多信息,请参阅 the section called “Advice ordering” 。)没有指定的排序意味着AOP子系统确定建议的顺序。

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

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

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

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

@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 注释的各种属性总结如下表:

Table 17.3. @Transactional Settings

属性类型描述
valueString可选限定符,指定要使用的事务管理器。
propagation枚举: Propagation可选的传播设置。
isolation枚举: Isolation可选隔离级别。仅适用于传播REQUIRED或REQUIRES_NEW。
timeoutint(以秒为单位)可选事务超时。仅适用于传播REQUIRED或REQUIRES_NEW。
readOnlyboolean读/写与只读事务。仅适用于REQUIRED或REQUIRES_NEW。
rollbackForClass 对象的数组,必须派生自 Throwable.必须导致回滚的可选异常类数组。
rollbackForClassName类名数组。类必须派生自 Throwable.必须导致回滚的可选异常类名称数组。
noRollbackForClass 对象的数组,必须派生自 Throwable.可选的异常类数组,不得导致回滚。
noRollbackForClassNameString 类名称的数组,必须派生自 Throwable.必须不会导致回滚的异常类名称的可选数组。

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

使用@Transactional的多个 Transaction 管理器

大多数Spring应用程序只需要一个事务管理器,但在某些情况下,您可能需要在单个应用程序中使用多个独立的事务管理器。 @Transactional 注释的value属性可用于选择性地指定要使用的 PlatformTransactionManager 的标识。这可以是bean名称或事务管理器bean的限定符值。例如,使用限定符表示法,以下Java代码

public class TransactionalService {

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

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

可以与应用程序上下文中的以下事务管理器bean声明结合使用。

<tx:annotation-driven/>

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

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

在这种情况下, TransactionalService 上的两个方法将在单独的事务管理器下运行,由 "order" 和 "account" 限定符区分。如果未找到特定限定的PlatformTransactionManager bean,仍将使用默认的 <tx:annotation-driven> 目标bean名称 transactionManager

自定义快捷方式注释

如果您发现在多种不同方法中重复使用 @Transactional 的相同属性,则 Spring’s meta-annotation support 允许您为特定用例定义自定义快捷方式注释。例如,定义以下注释

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

这里我们使用语法来定义事务管理器限定符,但也可以包含传播行为,回滚规则,超时等。

17.5.7 事务传播

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

在Spring管理的事务中,请注意物理和逻辑事务之间的区别,以及传播设置如何应用于此差异。

必填

tx prop required

PROPAGATION_REQUIRED

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

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

要求新

tx prop requires new

PROPAGATION_REQUIRES_NEW

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

嵌套

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

17.5.8 为 Transaction 操作提供咨询

假设您要执行事务性和一些基本性能分析建议。你如何在 <tx:annotation-driven/> 的背景下实现这一点?

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

  • 配置的性能分析方面启动。

  • 执行 Transaction 建议。

  • 建议对象的方法执行。

  • Transaction 提交。

  • 概要分析方面报告整个事务方法调用的确切持续时间。

本章不涉及详细解释AOP(除非适用于 Transaction )。有关以下AOP配置和AOP的详细介绍,请参见第11章,使用Spring进行面向方面编程。

这是上面讨论的简单分析方面的代码。建议的顺序通过 Ordered 界面控制。有关建议订购的完整详细信息,请参阅 the section called “Advice ordering” 。 。

package x.y;

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

public class SimpleProfiler implements Ordered {

    private int order;

    // 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,它以所需顺序对其应用了分析和事务方面。您可以以类似的方式配置任意数量的其他方面。

以下示例实现与上面相同的设置,但使用纯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"/>

    <!-- 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 属性的值,使其高于事务性建议的顺序值。

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

17.5.9 将@Transactional与AspectJ一起使用

也可以通过AspectJ方面在Spring容器外部使用Spring Framework的 @Transactional 支持。为此,首先使用 @Transactional 注释对类(以及可选的类的方法)进行注释,然后使用 spring-aspects.jar 文件中定义的 org.springframework.transaction.aspectj.AnnotationTransactionAspect 链接(编织)应用程序。还必须使用事务管理器配置方面。您当然可以使用Spring Framework的IoC容器来处理依赖注入方面。配置事务管理方面的最简单方法是使用 <tx:annotation-driven/> 元素并将 mode 属性指定为 aspectj ,如 Section 17.5.6, “Using @Transactional” 中所述。因为我们专注于在Spring容器外运行的应用程序,所以我们将向您展示如何以编程方式执行它。

在继续之前,您可能需要阅读第17.5.6节“使用@Transactional”和第11章,分别使用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);

使用此方面时,必须注释实现类(和/或该类中的方法),而不是该类实现的接口(如果有)。 AspectJ遵循Java的规则,即接口上的注释不会被继承。

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

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

要使用 AnnotationTransactionAspect 编写应用程序,您必须使用AspectJ构建应用程序(请参阅 AspectJ Development Guide )或使用加载时编织。见 Section 11.8.4, “Load-time weaving with AspectJ in the Spring Framework” 关于使用AspectJ进行加载时编织的讨论。

17.6 程序化 Transaction 管理

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

  • 使用 TransactionTemplate

  • 直接使用 PlatformTransactionManager 实现。

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

17.6.1 使用TransactionTemplate

TransactionTemplate 采用与其他Spring模板相同的方法,例如 JdbcTemplate 。它使用回调方法,使应用程序代码不必执行样板获取和释放事务资源,并产生意图驱动的代码,因为编写的代码仅关注开发人员想要做的事情。

正如您将在后面的示例中看到的那样,使用TransactionTemplate绝对会将您与Spring的事务基础结构和API结合在一起。程序化事务管理是否适合您的开发需求是您必须自己做出的决定。

必须在事务上下文中执行并且将显式使用_675607的应用程序代码如下所示。作为应用程序开发人员,您编写一个 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 (SomeBusinessExeption ex) {
            status.setRollbackOnly();
        }
    }
});

指定 Transaction 设置

您可以通过编程方式或在配置中在_675618上指定事务设置,例如传播模式,隔离级别,超时等。 TransactionTemplate 实例默认情况下具有 default transactional settings 。以下示例显示了特定 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 。然后可以将 sharedTransactionTemplate 注入到所需数量的服务中。

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

最后, TransactionTemplate 类的实例是线程安全的,因为实例不保持任何会话状态。但是, TransactionTemplate 实例会保持配置状态,因此虽然许多类可能共享 TransactionTemplate 的单个实例,但如果某个类需要使用具有不同设置的 TransactionTemplate (例如,不同的隔离级别),则需要创建两个不同的 TransactionTemplate 实例。

17.6.2 使用PlatformTransactionManager

您也可以直接使用 org.springframework.transaction.PlatformTransactionManager 来管理您的 Transaction 。只需通过bean引用将您正在使用的 PlatformTransactionManager 的实现传递给bean。然后,使用 TransactionDefinitionTransactionStatus 对象,您可以启动事务,回滚和提交。

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

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

只有在您进行少量事务操作时,程序化事务管理通常才是一个好主意。例如,如果您的Web应用程序仅需要针对某些更新操作进行事务处理,则可能不希望使用Spring或任何其他技术设置事务代理。在这种情况下,使用 TransactionTemplate 可能是一个很好的方法。能够明确设置事务名称也只能使用程序化的事务管理方法来完成。

另一方面,如果您的应用程序有许多事务操作,则声明式事务管理通常是值得的。它使事务管理不受业务逻辑的影响,并且不难配置。使用Spring Framework而不是EJB CMT时,声明式事务管理的配置成本大大降低。

17.8 事务绑定事件

从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 来覆盖该行为。

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

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

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

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

17.9.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 启动的挂起/恢复)。

17.9.2 Oracle WebLogic Server

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

17.10 常见问题的解决方案

17.10.1 对特定DataSource使用错误的事务管理器

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

17.11 更多资源

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

Updated at: 5 months ago
V. 数据访问Table of content18. DAO支持
Comment
You are not logged in.

There are no comments.