转载

Spring系列-事务管理

Spring Framework为事务管理提供了一致的抽象,具有以下优势:

  • 跨不同事务API的一致编程模型,例如Java Transaction API(JTA),JDBC,Hibernate,Java Persistence API(JPA)和Java Data Objects(JDO)。
  • 支持声明事务管理支持声明式事务管理。
  • 与复杂的事务API(如JTA)相比,用于编程事务管理的API更简单。
  • 与Spring的数据访问抽象集成。

Spring Framework的事务模型的优点

全局事务和本地事务

传统上,Java EE开发人员有两种事务管理选择:全局事务或本地事务,两者都有明显的局限性。

  • 全局事务:JTA、EJB 优点:可以多资源使用; 缺点:JTA API笨重、通过JNDI获取资源。
  • 本地事务:本地事务是资源专用,比如:JDBC连接。 优点:简单易用; 缺点:不能多资源使用。

Spring Framework的一致编程模型

Spring解决了全局和本地事务的缺点。它使应用程序开发人员能够在任何环境中使用一致的编程模型。Spring Framework提供了声明式和编程式事务管理。大多数用户更喜欢声明式事务管理,在大多数情况下建议使用。大多数用户更喜欢声明式事务管理,在大多数情况下建议使用。

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

理解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;
}
复制代码

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

TransactionDefinition

TransactionDefinition 用于描述事务的隔离级别、超时时间、是否只读事务和事务传播规则等控制事务具体行为的事务属性。

public interface TransactionDefinition {

    // 事务传播
    int getPropagationBehavior();

    // 事务隔离级别
    int getIsolationLevel();

    // 事务超时事务
    int getTimeout();

    // 是否只读
    boolean isReadOnly();

    // 事务名称
    String getName();

}
复制代码

TransactionStatus

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

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}
复制代码

PlatformTransactionManager

无论您是在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 定义将引用DataSource定义。

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

如果在Java EE容器中使用JTA,则使用通过JNDI获得的容器DataSource以及Spring的JtaTransactionManager。则配置如下:

<?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>
复制代码

Spring为不同的持久化框架提供了 PlatformTransactionManager 接口的实现类。如下表所示:

org.springframework.orm.jpa.JpaTransactionManager
org.springframework.orm.hibernateX.HibernateTransactionManager
org.springframework.jdbc.datasource.DataSourceTransactionManager
org.springframework.orm.jdo.JdoTransactionManager
org.springframework.transaction.jta.JtaTransactionManager

Synchronizing resources with transactions

本节描述直接或间接使用持久性API(如JDBC,Hibernate或JDO)的应用程序代码如何确保正确创建,重用和清理这些资源。

High-level synchronization approach

首选方法是使用Spring基于最高级别模板的持久性集成API,或者将ORM API与事务感知工厂bean或代理一起使用,以管理本地资源工厂。这些事务感知解决方案在内部处理资源创建和重用,清理,资源的可选事务同步以及异常映射。

因此,用户数据访问代码不必解决这些任务,但可以完全专注于非样板持久性逻辑。 通常,您使用本机ORM API或使用模板方法通过使用 JdbcTemplate 进行JDBC访问。

Low-level synchronization approach

Classes such as DataSourceUtils (for JDBC), EntityManagerFactoryUtils (for JPA), SessionFactoryUtils (for Hibernate), PersistenceManagerFactoryUtils (for JDO), and so on exist at a lower level. When you want the application code to deal directly with the resource types of the native persistence APIs, you use these classes to ensure that proper Spring Framework-managed instances are obtained, transactions are (optionally) synchronized, and exceptions that occur in the process are properly mapped to a consistent API.

For example, in the case of JDBC, instead of the traditional JDBC approach of calling the getConnection() method on the DataSource, you instead use Spring’s org.springframework.jdbc.datasource.DataSourceUtils class as follows:

Connection conn = DataSourceUtils.getConnection(dataSource);
复制代码

If an existing transaction already has a connection synchronized (linked) to it, that instance is returned. Otherwise, the method call triggers the creation of a new connection, which is (optionally) synchronized to any existing transaction, and made available for subsequent reuse in that same transaction. As mentioned, any SQLException is wrapped in a Spring Framework CannotGetJdbcConnectionException , one of the Spring Framework’s hierarchy of unchecked DataAccessExceptions. This approach gives you more information than can be obtained easily from the SQLException , and ensures portability across databases, even across different persistence technologies.

This approach also works without Spring transaction management (transaction synchronization is optional), so you can use it whether or not you are using Spring for transaction management.

声明式事务管理

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

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

从概念上讲,在事务代理上调用方法如下图所示:

Spring系列-事务管理

基于XML使用示例

// 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>
复制代码

<tx:advice/> 标记的 transaction-manager 属性设置为将驱动事务的PlatformTransactionManager bean的名称,在本例中为txManager bean。

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

验证代码如下:

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());
    }
}
复制代码

日志信息如下:

<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)
复制代码

回滚声明式事务

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

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

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

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

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

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

当Spring Framework的事务基础结构捕获异常并参考配置的回滚规则以确定是否将事务标记为回滚时,最强匹配规则将获胜。 因此,在以下配置的情况下,除 InstrumentNotFoundException 之外的任何异常都会导致后续事务的回滚。

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

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

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

不同的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>
复制代码

Using @Transactional

除了基于XML的事务配置声明方法之外,您还可以使用基于注释的方法。

// 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 -->
    <!-- a PlatformTransactionManager is still required -->
    <tx:annotation-driven transaction-manager="txManager"/>
    <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类即可。

Method visibility and @Transactional:

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

可以在接口定义,接口上的方法,类定义或类的公共方法之前放置@Transactional注释。 但是,仅仅存在 @Transactional 注释不足以激活事务行为。 @Transactional注释只是元数据,可由 @Transactional -aware的某些运行时基础结构使用,并且可以使用元数据来配置具有事务行为的适当bean。

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

Multiple Transaction Managers with @Transactional

大多数Spring应用程序只需要一个事务管理器,但在某些情况下,您可能需要在单个应用程序中使用多个独立的事务管理器。 @Transactional 注释的 value 属性可用于选择性地指定要使用的 PlatformTransactionManager 的标识。

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>
复制代码

如果您发现许多不同方法在@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 {
}
复制代码

应用自定义注释到 TransactionalService ,如下:

public class TransactionalService {

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

    @AccountTx
    public void doSomething() { ... }
}
复制代码

事务传播

Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法中。 TransactionDefinition 接口中规定了7种类型的事务传播行为,如下:

PROPAGATION_REQUIRED
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_REQUIRES_NEW
PROPAGATION_NOT_SUPPORTED
PROPAGATION_NEVER
PROPAGATION_NESTED

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

Required

Spring系列-事务管理

PROPAGATION_REQUIRED

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

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

RequiresNew

Spring系列-事务管理

PROPAGATION_REQUIRED 相比,PROPAGATION_REQUIRES_NEW对每个受影响的事务范围使用完全独立的事务。 在这种情况下,底层物理事务是不同的,因此可以独立提交或回滚,外部事务不受内部事务的回滚状态的影响。

Nested

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

Advising transactional operations

Suppose you want to execute both transactional and some basic profiling advice. How do you effect this in the context of <tx:annotation-driven/> ?

When you invoke the updateFoo(Foo) method, you want to see the following actions:

  • Configured profiling aspect starts up.
  • Transactional advice executes.
  • Method on the advised object executes.
  • Transaction commits.
  • Profiling aspect reports exact duration of the whole transactional method invocation.

Here is the code for a simple profiling aspect discussed above. The ordering of advice is controlled through the Ordered interface.

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>
复制代码

The following example effects the same setup as above, but uses the purely XML declarative approach.

<?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>
复制代码

Transaction bound event

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

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

@Service
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        return jdbcTemplate.queryForObject("SELECT * FROM  foo where name = ?", 
        new Object[]{fooName}, Foo.class);
    }

    @Transactional
    public void insertFoo(Foo foo) {
        jdbcTemplate.update("INSERT INTO foo(name) VALUES (?)",
                new Object[]{foo.getName()},
                new int[]{Types.VARCHAR});

        // 如果Foo没有继承ApplicationEvent, 则内部会包装为PayloadApplicationEvent。
        // 发布事件, 事务提交后, 记录日志, 或发送消息等操作。
        applicationEventPublisher.publishEvent(foo);
        //当事务提交后, 才会真正的执行@TransactionalEventListener配置的Listener, 因此Listener抛异常, 方法返回失败, 但事务不会回滚.
    }

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
}
复制代码
@Component
public class FooServiceListener {

    @TransactionalEventListener
    public void handler(PayloadApplicationEvent<FooService.Foo> creationEvent) {
        FooService.Foo foo =  creationEvent.getPayload();
        System.out.println("======"+foo.getName());
        System.out.println(1/0);
    }
}
复制代码

TransactionalEventListener 注释提供了一个阶段属性,该属性允许自定义侦听器应绑定到的事务的哪个阶段。 有效阶段是 BEFORE_COMMITAFTER_COMMIT (默认值), AFTER_ROLLBACK和AFTER_COMPLETION ,它们聚合事务完成(无论是提交还是回滚)。 如果没有正在运行的事务,则根本不调用侦听器,因为我们无法遵守所需的语义。 但是,可以通过将注释的 fallbackExecution 属性设置为 true 来覆盖该行为。

原文  https://juejin.im/post/5c695bcdf265da2dc675a903
正文到此结束
Loading...