1.TransactionManagement

1.TransactionManagement

全面的事务支持是使用 Spring Framework 的最令人信服的原因。 Spring 框架为事务 Management 提供了一致的抽象,具有以下优点:

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

  • 支持声明式 TransactionManagement

  • programmatic事务 Management 的 API 比 JTA 等复杂的事务 API 更简单。

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

1.1. Spring 框架的事务支持模型的优点(了解)

传统上,Java EE 开发人员在事务 Management 中有两种选择:全局或本地事务,这两者都有很大的局限性。下两节将回顾全局和本地事务 Management,然后讨论 Spring 框架的事务 Management 支持如何解决全局和本地事务模型的局限性。

1.1.1. GlobalTransaction

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

以前,使用全局事务的首选方法是通过 EJB CMT(容器 Management 的事务)。 CMT 是声明式事务 Management 的一种形式(与程序性事务 Management 不同)。 EJB CMT 消除了与事务相关的 JNDI 查找的需要,尽管使用 EJB 本身必须使用 JNDI。它消除了编写 Java 代码来控制事务的大部分(但不是全部)需求。重大缺点是 CMT 与 JTA 和应用程序服务器环境相关联。而且,仅当选择在 EJB 中(或至少在事务性 EJB 幕后之后)实现业务逻辑时,此功能才可用。通常,EJB 的缺点很大,以至于这不是一个有吸引力的主张,尤其是面对声明式事务 Management 的强制选择时。

1.1.2. 本地 Transaction

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

1.1.3. Spring 框架的一致编程模型

Spring 解决了 Global 和本地 Transaction 的弊端。它使应用程序开发人员可以在任何环境中使用一致的编程模型。您只需编写一次代码,即可从不同环境中的不同事务 Management 策略中受益。 Spring 框架提供了声明式和程序化事务 Management。大多数用户喜欢声明式事务 Management,在大多数情况下我们建议这样做。

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

您需要用于事务 Management 的应用服务器吗?

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

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

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

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

1.2. 了解 Spring 框架事务抽象

Spring 事务抽象的关键是事务策略的概念。 org.springframework.transaction.PlatformTransactionManager界面定义了一种 Transaction 策略,以下 Lists 显示了该策略:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

尽管您可以从应用程序代码中使用programmatically,但它主要是服务提供商接口(SPI)。由于PlatformTransactionManager是一个接口,因此可以根据需要轻松对其进行模拟或存根。它与诸如 JNDI 之类的查找策略无关。 PlatformTransactionManager实现的定义与 Spring Framework IoC 容器中的任何其他对象(或 bean)一样。单独使用此好处,即使使用 JTA,Spring 框架事务也成为有价值的抽象。与直接使用 JTA 相比,您可以更轻松地测试事务代码。  

同样,根据 Spring 的哲学,未选中可由PlatformTransactionManager接口的任何方法抛出的TransactionException(即,它扩展了java.lang.RuntimeException类)。Transaction 基础架构故障几乎总是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获并处理TransactionException。突出的一点是,不强制开发人员这样做。

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

TransactionDefinition接口指定:

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

    隔离度:此事务与其他事务的工作隔离的程度。例如,此事务能否看到其他事务未提交的写入?

  • 超时:超时之前该事务运行了多长时间,并被基础事务基础结构自动回滚。

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

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

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

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

无论您在 Spring 中选择声明式还是程序化事务 Management,定义正确的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
        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(或任何其他特定资源),因为它使用了容器的全局事务 Management 基础结构。  

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

DataSource bean 定义与先前显示的本地 JDBC 示例相似,因此在以下示例中未显示。

Note

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

在这种情况下,txManager bean 是HibernateTransactionManager类型。就像DataSourceTransactionManager需要引用DataSource一样,HibernateTransactionManager也需要引用SessionFactory。以下示例声明sessionFactorytxManager bean:

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

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

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

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

  

Note

如果您使用 JTA,则无论使用哪种数据访问技术(无论是 JDBC,Hibernate JPA 或任何其他受支持的技术),事务 Management 器定义都应该看起来相同。这是由于 JTA 事务是全局事务,它可以征用任何事务资源。

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

1.3. 将资源与事务同步

现在应该清楚如何创建不同的事务 Management 器,以及如何将它们链接到需要同步到事务的相关资源(例如,DataSourceTransactionManager到 JDBC DataSourceHibernateTransactionManager到 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 CannotGetJdbcConnectionException是未经检查的DataAccessException类型的层次结构之一。与从SQLException轻松获得的信息相比,这种方法为您提供的信息更多,并确保了跨数据库甚至跨不同持久性技术的可移植性。  

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

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

1.3.3. TransactionAwareDataSourceProxy

最低级别是TransactionAwareDataSourceProxy类。这是目标DataSource的代理,该代理包装了目标DataSource以增加对 SpringManagement 的事务的了解。在这方面,它类似于 Java EE 服务器提供的事务性 JNDI DataSource

您几乎永远不需要或不想使用此类,除非必须调用现有代码并通过标准的 JDBC DataSource接口实现。在这种情况下,该代码可能可用,但参与了 SpringManagement 的事务。您可以使用前面提到的高级抽象来编写新代码。

1.4. 声明式 TransactionManagement

Note

大多数 Spring Framework 用户选择声明式事务 Management。此选项对应用程序代码的影响最小,因此与无创轻量级容器的 IDEA 最一致。

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

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

  • 与绑定到 JTA 的 EJB CMT 不同,Spring 框架的声明式事务 Management 可在任何环境中工作。它可以通过使用 JDBC,JPA 或 Hibernate 通过调整配置文件来处理 JTA 事务或本地事务。

  • 您可以将 Spring Framework 声明式事务 Management 应用于任何类,而不仅限于 EJB 之类的特殊类。

  • Spring 框架提供了声明性的rollback rules,此功能没有 EJB 等效项。提供了对回滚规则的编程和声明性支持。

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

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

TransactionProxyFactoryBean 在哪里?

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

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

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

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

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

仅仅告诉您使用@Transactional注解对类进行注解,将@EnableTransactionManagement添加到您的配置中,并希望您了解其全部工作原理是不够的。为了提供更深入的理解,本节介绍了在发生与事务相关的问题时,Spring 框架的声明式事务基础结构的内部工作方式。

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

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

1.4.2. 声明式事务实现示例

考虑以下接口及其附带的实现。本示例使用FooBar类作为占位符,以便您可以专注于事务使用而不关注特定的域模型。就本示例而言,DefaultFooService类在每个已实现方法的主体中引发UnsupportedOperationException实例的事实是很好的。该行为使您可以看到已创建事务,然后响应UnsupportedOperationException实例而回滚了事务。以下 Lists 显示了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)必须在具有 read-语义的事务上下文中执行写语义。以下几节将详细说明以下配置:  

<!-- 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 成为事务性的。要应用的事务语义封装在<tx:advice/>定义中。 <tx:advice/>的定义为“从get开始的所有方法都将在只读事务的上下文中执行,而所有其他方法将以默认的事务语义执行”。 <tx:advice/>标记的transaction-manager属性设置为将要驱动事务的PlatformTransactionManager bean 的名称(在本例中为txManager bean)。  

Tip

如果要连接的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:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>

  

Note

在前面的示例中,假定您的所有服务接口都在x.y.service包中定义。

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

前面显示的配置用于围绕从fooService bean 定义创建的对象创建事务代理。代理配置有事务建议,以便在代理上调用适当的方法时,根据与该方法相关联的事务配置,事务将被启动,挂起,标记为只读等。考虑下面的程序,该程序测试驱动前面显示的配置:

public final class Boot {

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

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

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

<!-- 创建DefaultFooService代理-->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

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

<!-- 事务通知在这里开始... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [[emailprotected]] for JDBC transaction

<!--insertFoo(..) 方法引发异常... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- 事务被回滚(默认情况下,RuntimeException实例导致回滚) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [[emailprotected]]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- 为清晰起见,删除了AOP基础架构堆栈跟踪元素 -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

  

1.4.3. 回滚声明式事务

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

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

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

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

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

如果您不希望在引发异常时回滚事务,则还可以指定“无回滚规则”。下面的示例告诉 Spring 框架的事务基础结构,即使面对未处理的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 配置不同的事务语义

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

作为比较,首先假定所有服务层类都在根x.y.service包中定义。要使所有在该包(或子包)中定义的类实例且名称以Service结尾的 bean 具有默认的事务配置,可以编写以下代码:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config>

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

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

    </aop:config>

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

  

1.4.5. <tx:advice/>设置

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

  • propagation settingREQUIRED.

  • 隔离级别为DEFAULT.

  • 事务是读写的。

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

  • 任何RuntimeException都会触发回滚,而所有选中的Exception都不会触发。

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

表 1.<tx:method/>设置

AttributeRequired?DefaultDescription
name Yes 与事务属性关联的方法名称。通配符(*)可用于将相同的事务属性设置与多种方法(例如get*handle*on*Event等)相关联。
propagation No REQUIRED 事务传播行为。
isolation No DEFAULT 事务隔离级别。仅适用于REQUIREDREQUIRES_NEW的传播设置。
timeout No -1 事务超时(秒)。仅适用于传播REQUIREDREQUIRES_NEW
read-only No false 读写与只读事务。仅适用于REQUIREDREQUIRES_NEW
rollback-for No 触发回滚的Exception个实例的逗号分隔列表。例如com.foo.MyBusinessException,ServletException.
no-rollback-for No 逗号分隔的Exception个实例列表,不会触发回滚。例如com.foo.MyBusinessException,ServletException.

1.4.6. 使用@Transactional

除了基于 XML 的声明式方法进行事务配置外,还可以使用基于 Comments 的方法。直接在 Java 源代码中声明事务语义会使声明更接近受影响的代码。不存在过度耦合的危险,因为原本打算以事务方式使用的代码几乎总是以这种方式部署。 

使用@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);
}

在上面的类级别使用,Comments 指示声明类(及其子类)的所有方法的默认值。另外,每种方法都可以单独 Comments。注意,类级别的 Comments 不适用于类层次结构中的祖先类。在这种情况下,需要在本地重新声明方法,以参与子类级别的 Comments。  

当一个 POJO 类(例如上面的一个)在 Spring 上下文中定义为 bean 时,您可以通过@Configuration类中的@EnableTransactionManagement注解使 bean 实例具有事务性。

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

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

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

    <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>
  • (1) 使 bean 实例具有事务性的行。

 

Tip

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

 

方法可见性和@Transactional

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

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

Tip

Spring 团队建议您仅使用@Transactional注解对具体类(以及具体类的方法)进行注解,而不是对接口进行注解。您当然可以在接口(或接口方法)上放置@Transactional注解,但这仅在您使用基于接口的代理时才可以预期地起作用。 Java 注解不从接口继承的事实意味着,如果您使用基于类的代理(proxy-target-class="true")或基于编织的方面(mode="aspectj"),则代理和编织基础结构无法识别事务设置,并且该对象是没有包装在 Transaction 代理中。

Note

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

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

表 2.Comments 驱动的事务设置

XML AttributeAnnotation AttributeDefaultDescription
transaction-manager 不适用(请参见TransactionManagementConfigurer javadoc) transactionManager 要使用的事务 Management 器的名称。如上例所示,仅当事务 Management 器的名称不是transactionManager时才需要。
mode mode proxy 缺省模式(proxy)使用 Spring 的 AOP 框架处理要注解的 bean(遵循代理语义,如前所述,仅适用于通过代理传入的方法调用)。替代模式(aspectj)则使用 Spring 的 AspectJ 事务方面来编织受影响的类,修改目标类字节码以应用于任何类型的方法调用。 AspectJ 编织在 Classpath 中需要spring-aspects.jar,并且启用了加载时编织(或编译时编织)。 
proxy-target-class proxyTargetClass false 仅适用于proxy模式。控制为带有@Transactional注解的类创建哪种类型的事务代理。如果proxy-target-class属性设置为true,则会创建基于类的代理。如果proxy-target-classfalse或省略了属性,则将创建基于标准 JDK 接口的代理。
order order Ordered.LOWEST_PRECEDENCE 定义应用于带有@Transactional注解的 bean 的事务通知的 Sequences。 

Note

处理@Transactional注解的默认建议模式是proxy,该模式仅允许通过代理拦截呼叫。同一类内的本地调用无法以这种方式被拦截。

Note

proxy-target-class属性控制为使用@Transactional注解标注的类创建哪种类型的事务代理。如果proxy-target-class设置为true,则会创建基于类的代理。如果proxy-target-classfalse或省略了属性,则将创建基于标准 JDK 接口的代理。

Note

@EnableTransactionManagement<tx:annotation-driven/>仅在定义它们的相同应用程序上下文中的 Bean 上寻找@Transactional。这意味着,如果将注解驱动的配置放在_6 的WebApplicationContext中,则仅在控制器而不是服务中检查@Transactional bean。

在评估方法的事务设置时,最派生的位置优先。在下面的示例中,使用只读事务的设置在类级别注解sDefaultFooService类,但是同一类中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.

  • 事务是读写的。

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

  • 任何RuntimeException都会触发回滚,而所有选中的Exception都不会触发。

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

表 3. @Transaction 设置

PropertyTypeDescription
value String 可选的限定词,指定要使用的事务 Management 器。
propagation enum : Propagation 可选的传播设置。
isolation enum : Isolation 可选的隔离级别。仅适用于REQUIREDREQUIRES_NEW的传播值。
timeout int(以秒为单位) 可选的事务超时。仅适用于REQUIREDREQUIRES_NEW的传播值。
readOnly boolean 读写与只读事务。仅适用于REQUIREDREQUIRES_NEW的值。
rollbackFor Class个对象的数组,必须从Throwable.派生 必须引起回滚的异常类的可选数组。
rollbackForClassName 类名数组。这些类必须源自Throwable. 必须引起回滚的异常类名称的可选数组。
noRollbackFor Class个对象的数组,必须从Throwable.派生 不能导致回滚的异常类的可选数组。
noRollbackForClassName String类名称的数组,必须从Throwable.派生 不能引起回滚的异常类名称的可选数组。

当前,您无法对事务名称进行显式控制,其中“名称”是指显示在事务监视器(如果适用)(例如,WebLogic 的事务监视器)和日志输出中的事务名称。对于声明性事务,事务名称始终是完全合格的类名称.事务建议类的方法名称。例如,如果BusinessService类的handlePayment(..)方法启动了事务,则事务的名称应为:com.example.BusinessService.handlePayment

具有@Transactional 的多个事务管理器

大多数 Spring 应用程序仅需要一个事务 Management 器,但是在某些情况下,您可能需要在一个应用程序中使用多个独立的事务 Management 器。您可以使用@Transactional注解的value属性来指定要使用的PlatformTransactionManager的标识。这可以是 bean 名称,也可以是事务 Management 器 bean 的限定符值。例如,使用限定符表示法,可以在应用程序上下文中将以下 Java 代码与以下事务 Management 器 bean 声明进行组合:

public class TransactionalService {

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

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

以下 Lists 显示了 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上的两种方法在单独的事务管理器下运行,并以orderaccount限定词进行区分。如果未找到特别限定的PlatformTransactionManager bean,仍将使用默认的<tx:annotation-driven>目标 bean 名称transactionManager  

自定义快捷方式注解

如果发现您在许多不同的方法上重复使用@Transactional的相同属性,则可以使用Spring 的元 Comments 支持为特定用例定义自定义快捷方式 Comments。例如,考虑以下 Comments 定义:

@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 {
}

前面的 Comments 使我们可以编写上一节中的示例,如下所示:  

public class TransactionalService {

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

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

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

1.4.7. Transaction 传播

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

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

理解 PROPAGATION_REQUIRED

 PROPAGATION_REQUIRED强制执行物理事务,如果尚不存在,则在当前范围内本地执行,或参与为较大范围定义的现有“外部”事务。这是同一线程(例如,委派给几种存储库方法的服务立面,所有基础资源都必须参与服务级事务的服务立面)的优良默认设置。

Note

默认情况下,参与的事务将加入外部作用域的 Feature,而忽略本地隔离级别,超时值或只读标志(如果有)。如果要在参与具有不同隔离级别的现有事务时拒绝隔离级别声明,请考虑在事务 Management 器上将validateExistingTransactions标志切换为true。这种非宽容模式还拒绝只读不匹配(即,内部读写事务试图参与只读外部作用域)。

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

但是,在内部事务范围设置仅回滚标记的情况下,外部事务尚未决定回滚本身,因此回滚(由内部事务范围默默触发)是意外的。此时将抛出一个对应的UnexpectedRollbackException。这是预期的行为,因此事务调用者永远不会被误认为是在确实未执行提交的情况下进行的。因此,如果内部事务(外部调用者不知道)将事务无提示地标记为仅回滚,则外部调用者仍会调用 commit。外部呼叫者需要接收UnexpectedRollbackException来明确指示已执行回滚。

理解 PROPAGATION_REQUIRES_NEW

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

理解 PROPAGATION_NESTED

PROPAGATION_NESTED使用具有多个可还原到的保存点的单个物理事务。这种部分回滚使内部事务范围触发其范围的回滚,尽管某些操作已回滚,但外部事务仍能够 continue 物理事务。此设置通常 Map 到 JDBC 保存点,因此仅适用于 JDBC 资源事务。

1.4.8. Transaction 事务建议

假设您要执行事务性操作和一些基本的分析建议。您如何在<tx:annotation-driven/>的情况下实现此目的?

调用updateFoo(Foo)方法时,您想要查看以下操作:

  • 配置的外观方面开始。

  • Transaction 通知执行。

  • 通知对象上的方法执行。

  • 事务提交。

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

Note

本章不涉及任何详细的 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 具有按所需 Sequences 应用的概要分析和事务方面:

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

  

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

面的示例创建与前两个示例相同的设置,但是使用纯 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 依次具有概要分析和事务方面的内容。如果您希望性能分析建议在进来的事务处理建议之后和之后的事务处理建议之前执行,则可以交换性能分析方面 Bean 的order属性的值,以使其高于事务处理建议的订单值。

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

  

1.4.9. 在 AspectJ 中使用@Transactional

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

以下示例显示了如何创建事务 Management 器并配置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);

  

Note

使用此切面时,必须注解实现类(或该类中的方法或两者),而不是注解该类所实现的接口(如果有)。 AspectJ 遵循 Java 的规则,即不继承接口上的注解。

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

类中方法上的@Transactional注解将覆盖类注解(如果存在)给出的默认事务语义。您可以注解任何方法,而不管可见性如何。

要使用AnnotationTransactionAspect编织应用程序,您必须使用 AspectJ 来构建应用程序(请参见AspectJ 开发指南)或使用加载时编织。

1.5. 程序化 TransactionManagement

Spring 框架通过使用以下两种方式提供程序化事务 Management 的方法:

  • TransactionTemplate

  • 直接执行PlatformTransactionManager

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

1.5.1. 使用 TransactionTemplate

TransactionTemplate采用与其他 Spring 模板(例如JdbcTemplate)相同的方法。它使用一种回调方法(使应用程序代码不必进行样板获取和释放事务性资源),并生成意向驱动的代码,因为您的代码仅专注于您要执行的操作。

Note

如以下示例所示,使用TransactionTemplate绝对可以使您与 Spring 的事务基础结构和 API 耦合。程序化事务 Management 是否适合您的开发需求是您必须自己做的决定。

必须在事务上下文中执行并且显式使用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();
        }
    }
});

  

指定 Transaction 设置

您可以以编程方式或配置方式在TransactionTemplate上指定事务设置(例如传播模式,隔离级别,超时等)。默认情况下,TransactionTemplate实例具有默认 Transaction 设置

以下示例显示了针对特定TransactionTemplate:的 Transaction 设置的编程自定义

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来管理您的 Transaction。为此,请通过 bean 引用将您使用的PlatformTransactionManager的实现传递给 bean。然后,通过使用TransactionDefinitionTransactionStatus对象,您可以启动事务,回滚和提交。以下示例显示了如何执行此操作:

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 显式设置事务名称只能通过编程来完成
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. 在程序性和声明性事务 Management 之间进行选择

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

另一方面,如果您的应用程序具有大量事务操作,则声明式事务 Management 通常是值得的。它使事务 Management 脱离业务逻辑,并且不难配置。当使用 Spring 框架而不是 EJB CMT 时,声明式事务 Management 的配置成本大大降低了。

1.7. Transaction 绑定事件

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

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

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

@Component
public class MyComponent {

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

@TransactionalEventListener注解公开了phase属性,该属性使您可以自定义应将侦听器绑定到的事务阶段。有效阶段为BEFORE_COMMITAFTER_COMMIT(默认),AFTER_ROLLBACKAFTER_COMPLETION,这些阶段汇总事务完成(提交或回滚)。

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

1.8. 应用服务器特定的集成

Spring 的事务抽象通常与应用程序服务器无关。此外,Spring 的JtaTransactionManager类(可以选择对 JTA UserTransactionTransactionManager对象执行 JNDI 查找)自动检测后一个对象的位置,该位置随应用程序服务器的不同而不同。可以访问 JTA TransactionManager来增强事务语义-特别是支持事务挂起。

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

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

1.8.1. IBM WebSphere

在 WebSphere 6.1.0.9 和更高版本上,推荐使用的 Spring JTA 事务 Management 器是WebSphereUowTransactionManager。这个特殊的适配器使用 IBM 的UOWManager API,WebSphere Application Server 6.1.0.9 和更高版本中提供了该 API。使用此适配器,IBM 正式支持 Spring 驱动的事务挂起(由PROPAGATION_REQUIRES_NEW发起的挂起和 continue)。

1.8.2. Oracle WebLogic 服务器

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

 

1.9. 常见问题的解决方案

1.9.1. 对特定的数据源使用错误的事务 Management 器

根据您选择的 Transaction 技术和要求,使用正确的PlatformTransactionManager实现。如果使用得当,Spring 框架仅提供了直接且可移植的抽象。如果使用全局事务,则必须对所有事务操作使用org.springframework.transaction.jta.JtaTransactionManager类(或其中的应用服务器特定的子类)。否则,事务基础结构将尝试在诸如容器DataSource实例之类的资源上执行本地事务。这样的本地事务是没有意义的,好的应用服务器会将它们视为错误。

  

原文地址:https://www.cnblogs.com/jrkl/p/14278289.html