SpringBoot源码之事物 Transaction官方文档翻译

官方文档地址:

https://docs.spring.io/spring/docs/4.3.21.RELEASE/spring-framework-reference/htmlsingle/#transaction

介绍Spring框架事务管理

全面的事务支持是使用Spring框架的最令人信服的原因之一。Spring框架为事务管理提供了一致的抽象,提供了以下好处:

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

下面几节描述Spring框架的事务增值和技术。(本章还讨论了最佳实践、应用服务器集成和常见问题的解决方案。)

  • Spring框架事务支持模型的优点描述了为什么要使用Spring框架的事务抽象而不是EJB容器管理事务(CMT),或者选择通过Hibernate等专有API驱动本地事务。
  • 理解Spring Framework事务抽象概括了核心类,并描述了如何配置和从各种源获取数据源实例。
  • 将资源与事务同步描述应用程序代码如何确保资源被正确创建、重用和清理。
  • 声明式事务管理描述对声明式事务管理的支持。
  • 程序化事务管理包括对程序化(即显式编码)事务管理的支持。
  • 事务绑定事件描述如何在事务中使用应用程序事件。

Spring框架事务支持模型的优点

传统上,Java EE开发人员有两种事务管理选择:全局事务或本地事务,这两种事务都有深刻的局限性。在接下来的两个部分中,将回顾全局和本地事务管理,然后讨论Spring框架的事务管理支持如何解决全局和本地事务模型的局限性。

全局事务使您能够处理多个事务资源,通常是关系数据库和消息队列。应用服务器通过JTA管理全局事务,JTA使用起来很麻烦(部分原因是它的异常模型)。此外,JTA UserTransaction通常需要来自JNDI,这意味着您还需要使用JNDI才能使用JTA。显然,使用全局事务会限制应用程序代码的任何潜在重用,因为JTA通常只在应用服务器环境中可用。

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

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

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

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

您是否需要一个用于事务管理的应用服务器?

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

特别地,您不需要仅仅为了通过ejb进行声明性事务而使用应用服务器。事实上,即使您的应用服务器具有强大的JTA功能,您也可能认为Spring框架的声明性事务提供了比EJB CMT更强大的功能和更高效的编程模型。

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

Spring框架让您可以选择何时将应用程序扩展到完全加载的应用服务器。使用EJB CMT或JTA的唯一替代方法是使用本地事务(如JDBC连接上的事务)编写代码的日子已经一去不复返了,如果需要这些代码在全局容器管理的事务中运行,则需要进行大量的重新工作。使用Spring框架,只需要更改配置文件中的一些bean定义,而不需要更改代码。

理解Spring框架事务抽象

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

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

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

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

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

TransactionDefinition接口指定:

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

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

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数据源:

<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定义将具有对数据源定义的引用。它看起来是这样的:

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

如果您在Java EE容器中使用JTA,那么您将使用通过JNDI获得的容器数据源,以及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不需要了解数据源或任何其他特定资源,因为它使用容器的全局事务管理基础设施。

上面数据源bean的定义使用来自jee名称空间的<jndi-lookup/>标记。有关基于模式的配置的更多信息,请参见第41章基于XML模式的配置,以及关于<jee/>标记的更多信息,请参见标题为41.2.3节的“jee模式”一节。

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

数据源bean定义将类似于前面显示的本地JDBC示例,因此在下面的示例中不会显示。

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

正如DataSourceTransactionManager需要对数据源的引用一样,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事务是全局事务,它可以征募任何事务资源。

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

使资源与事务同步

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

高级的同步方法:

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

低级的同步方法:

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

例如,在JDBC的情况下,不使用在数据源上调用getConnection()方法的传统JDBC方法,而是使用Spring的org.springframe.jdbc.datasource.DataSourceUtils类如下:

Connection conn = DataSourceUtils.getConnection(dataSource);

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

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

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

在最底层存在TransactionAwareDataSourceProxy类。这是目标数据源的代理,它包装目标数据源以增加spring管理的事务的感知。在这方面,它类似于Java EE服务器提供的事务JNDI数据源。

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

声明式事务管理

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

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

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

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

Where is TransactionProxyFactoryBean?

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

spring 2.0之前的配置样式仍然是100%有效的配置;将新的<tx:tags/>简单地定义代表您的TransactionProxyFactoryBean。

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

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

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

仅仅告诉您使用@Transactional注释注释类,将@EnableTransactionManagement添加到配置中,然后期望您理解它的工作原理是不够的。本节将解释Spring框架声明性事务基础结构在发生与事务相关的问题时的内部工作方式。

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

从概念上讲,在事务代理上调用方法是这样的……

声明性事务实现的示例:

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

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}
package x.y.service;

public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        throw new UnsupportedOperationException();
    }

    public Foo getFoo(String fooName, String barName) {
        throw new UnsupportedOperationException();
    }

    public void insertFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

    public void updateFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

}

假设FooService接口的前两个方法getFoo(String)和getFoo(String, String)必须在具有只读语义的事务上下文中执行,而其他方法insertFoo(Foo)和updateFoo(Foo)必须在具有读写语义的事务上下文中执行。下面几段将详细解释以下配置。

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

    <!-- 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属性设置为平台transactionmanager bean的名称,该bean将驱动事务,在本例中是txManager bean。

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

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

一个常见的需求是使整个服务层具有事务性。最好的方法是简单地更改切入点表达式,使之与服务层中的任何操作匹配。例如:

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

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

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

public final class Boot {

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

运行前一个程序的输出类似于下面的输出。(Log4J输出和来自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)

回滚声明性事务:

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

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

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

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

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

如果不希望在抛出异常时回滚事务,还可以指定“无回滚规则”。下面的示例告诉Spring框架的事务基础结构,即使面对未处理的InstrumentNotFoundException,也要提交附带的事务。

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

当Spring框架的事务基础设施捕捉到异常并参考配置的回滚规则以确定是否将事务标记为回滚时,最匹配的规则获胜。因此,在以下配置的情况下,除了InstrumentNotFoundException之外的任何异常都会导致随附事务的回滚。

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    </tx:attributes>
</tx:advice>

还可以通过编程方式指示所需的回滚。虽然非常简单,但是这个过程是非常具有侵入性的,并且将您的代码紧密地耦合到Spring框架的事务基础结构中:

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

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

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

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

作为比较,首先假设您的所有服务层类都是在根x.y中定义的。服务包。要使包(或子包)中定义的类实例以及名称以服务结尾的所有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>

<tx:advice/> settings:

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

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

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

Table 17.1. <tx:method/> settings

Attribute Required? Default Description

name

Yes

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

propagation

No

REQUIRED

事物传播行为

isolation

No

DEFAULT

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

timeout

No

-1

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

read-only

No

false

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

rollback-for

No

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

no-rollback-for

No

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

使用@Transactional:

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

标准javax.transaction。事务性注释也被支持作为Spring自身注释的替换。更多细节请参考JTA 1.2文档。

@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:注解驱动/>标记中的transaction-manager属性。如果您希望依赖注入的PlatformTransactionManager bean有任何其他名称,那么您必须显式地使用transaction-manager属性,如前面的示例所示。

如果使用基于Java的配置,@EnableTransactionManagement注释提供了等价的支持。只需将注释添加到@Configuration类。有关详细信息,请参见javadoc。

方法可见性和@Transactional:

在使用代理时,应该只将@Transactional注释应用于具有公共可见性的方法。如果使用@Transactional注释对受保护的、私有的或包可见的方法进行注释,则不会引发错误,但是注释的方法不会显示配置的事务设置。如果需要对非公共方法进行注释,请考虑使用AspectJ(参见下面)。

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

Spring建议您只使用@Transactional注释注释具体类(以及具体类的方法),而不是注释接口。当然,您可以将@Transactional注释放置在接口(或接口方法)上,但是只有在使用基于接口的代理时,它才会像您所期望的那样工作。Java注释的事实并不意味着继承接口如果您使用的是基于类的代理(proxy-target-class = " true ")或weaving-based方面(模式=“aspectj”),然后由代理事务设置不承认和编织的基础设施,和对象将不会被包裹在一个事务代理,这将是绝对不好。

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

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

Table 17.2. Annotation driven transaction settings

XML Attribute Annotation Attribute Default Description

transaction-manager

N/A (See TransactionManagementConfigurerjavadocs)

transactionManager

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

mode

mode

proxy

默认模式“代理”使用Spring的AOP框架代理带注释的bean(按照前面讨论的代理语义,仅应用于通过代理传入的方法调用)。替代模式“aspectj”将受影响的类与Spring的aspectj事务方面编织在一起,修改目标类字节码以应用于任何类型的方法调用。AspectJ编织需要spring方面。类路径中的jar以及加载时编织(或编译时编织)启用。(有关如何设置加载时编织的详细信息,请参阅“Spring配置”一节。)

proxy-target-class

proxyTargetClass

false

仅适用于代理模式。控件为使用@Transactional注释注释的类创建哪种类型的事务代理。如果代理目标类属性设置为true,则创建基于类的代理。如果proxy-target-class为假,或者属性被省略,那么将创建基于JDK接口的标准代理。(有关不同代理类型的详细研究,请参见第11.6节“代理机制”)。

order

order

Ordered.LOWEST_PRECEDENCE

定义应用于带有@Transactional注释的bean的事务通知的顺序。(有关AOP通知排序规则的更多信息,请参见“通知排序”一节)。没有指定的顺序意味着AOP子系统决定了通知的顺序。

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

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

@EnableTransactionManagement和<tx:注解驱动/>仅在定义bean的相同应用程序上下文中查找@Transactional。这意味着,如果您将注释驱动配置放在一个DispatcherServlet的WebApplicationContext中,那么它只检查控制器中的@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
  • 事物是read/write
  • 事务超时默认为基础事务系统的默认超时,如果不支持超时,则为none。
  • 任何RuntimeException都会触发回滚,而任何已检查的异常都不会。

这些默认设置可以更改;@Transactional注释的各种属性总结如下表所示:

Property Type Description

value

String

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

propagation

enum: Propagation

可选的传播行为。

isolation

enum: Isolation

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

timeout

int (in seconds granularity)

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

readOnly

boolean

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

rollbackFor

Array of Class objects, which must be derived from Throwable.

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

rollbackForClassName

Array of class names. Classes must be derived from Throwable.

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

noRollbackFor

Array of Class objects, which must be derived from Throwable.

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

noRollbackForClassName

Array of String class names, which must be derived from Throwable.

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

目前,您无法显式控制事务的名称,其中“名称”指的是将在事务监视器(如WebLogic的事务监视器)和日志输出中显示的事务名称。对于声明性事务,事务名始终是完全限定的类名+ "。+事务建议类的方法名。例如,如果BusinessService类的handlePayment(..)方法启动了一个事务,那么该事务的名称将是:com.foo.BusinessService.handlePayment。

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

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

public class TransactionalService {

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

    @Transactional("account")
    public void doSomething() { ... }
}
<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:注解驱动的>目标bean名称transactionManager。

自定义快捷注解:

如果您发现在许多不同的方法上重复使用@Transactional的相同属性,那么Spring的元注释支持允许您为特定的用例定义定制的快捷注释。例如,定义以下注释:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

允许我们将前一节中的示例写成

public class TransactionalService {

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

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

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

传播行为:

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

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

tx prop required

PROPAGATION_REQUIRED

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

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

tx prop requires new

PROPAGATION_REQUIRES_NEW

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

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

建议事务操作:

假设您希望同时执行事务性和一些基本的分析建议。如何在<tx:注解驱动/>的上下文中实现这一点?

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

  • 启动配置的分析切面。
  • 事物advice执行
  • 目标方法执行
  • 事务提交
  • 分析方面报告整个事务方法调用的精确持续时间。

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

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

事物绑定事件:

从Spring 4.2开始,事件的侦听器可以绑定到事务的某个阶段。典型的例子是在事务成功完成时处理事件:这允许在当前事务的结果对侦听器有实际影响时更灵活地使用事件。

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

让我们举个例子来说明这个概念。假设一个组件发布了一个订单创建的事件,我们希望定义一个侦听器,该侦听器只应在发布该事件的事务成功提交后处理该事件:

@Component
public class MyComponent {

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

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

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

特定于应用服务器的集成:

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

Spring的JtaTransactionManager是在Java EE应用程序服务器上运行的标准选择,众所周知,它可以在所有公共服务器上运行。高级功能(如事务挂起)也可以在许多服务器上工作——包括GlassFish、JBoss和Geronimo——不需要任何特殊配置。但是,为了完全支持事务挂起和进一步的高级集成,Spring为WebLogic Server和WebSphere提供了特殊的适配器。下面几节将讨论这些适配器。

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

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

猜你喜欢

转载自blog.csdn.net/AnY11/article/details/84837899