Spring事务总结

一、声明式事务 @Transactional

 

  在事务代理上调用方法的执行路径示意图:

 

  @Transactional注解配置

  默认配置:

    1、传播行为 PROPAGATION_REQUIRED

      2、隔离级别 ISOLATION_DEFAULT

    3、事务是读写的 read-write

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

    5、任何 RuntimeException  异常触发回滚,但是任何已检查异常不会

   

  @Transactional注解属性介绍

  1、propagation Spring事务传播机制:

PROPAGATION_REQUIRED Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。
PROPAGATION_SUPPORTS 如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。
PROPAGATION_MANDATORY 该传播级别要求上下文中必须存在事务,否则抛出异常。
PROPAGATION_REQUIRES_NEW 该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)
PROPAGATION_NOT_SUPPORTED 当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)
PROPAGATION_NEVER 该传播级别要求上下文中不能存在事务,否则抛出异常。
PROPAGATION_NESTED 嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务。(save point概念)

  常用传播机制:

    1、PROPAGATION_REQUIRED

         

    Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。

 

    2、PROPAGATION_REQUIRES_NEW

    

    该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)  

 

     3、PROPAGATION_NESTED

    嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务。

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

    

 

  2、isolation 隔离级别 

DEFAULT 使用底层数据库默认的隔离级别
READ_UNCOMMITTED 读未提交
READ_COMMITTED 读已提交
REPEATABLE_READ 可重复度
SERIALIZABLE 串行化

  

  3、timeout 事务超时时间

  默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务

  4、readOnly 事务是否为只读事务

   默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

  5、rollbackFor 

  指定能够触发事务回滚的异常类型,可以指定多个异常类型。

  6、noRollbackFor

  抛出指定的异常类型,不回滚事务,也可以指定多个异常类型

 

 

 总结:

  注意:spring声明式事务是基于aop实现的。(只有来自外部方法的调用才会被aop代理捕获,类的内部方法调用不会被aop代理,即使此方法上面加了@Transactional注解)

     Spring默认回滚 RuntimeException和error 异常

 

 

 

 Spring声明式事务失效的几种情况:

  1、事务方法不是public修饰的

    Spring官方文档说明:When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

    即:@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

 

  2、方法用final修饰,或者static修饰的静态方法

    Spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能

    如果某个方法用final修饰或者static修饰,那么在它的代理类中,就无法重写该方法,而添加事务功能 

 

  3、自身调用问题

    ①:REQUIRED 传播机制下,没有@Transactional 标识的非事务方法内部调用  @Transactional标识的事务方法,事务失效

    ②:@Transactional标识的方法内部调用了 @Transactional(propagation = Propagation.REQUIRES_NEW) 标识的新开启一个事务的方法,这个新开的事务 不会生效。  不过,如果发生了异常,是可以回滚的。

    因为发生自身调用的时候,就调用该类自己的方法,而没有经过Spring的代理类,默认只有在外部调用事务才会生效。

 

  4、异常被方法内部捕获,没有向上抛出

    如果必须要捕获异常,请在finally代码块中手动回滚:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

 

  5、异常类型不符

    Spring事务默认回滚的是 RuntimeException(Exception的子类) ,如果你想触发其他异常的回滚,如不是RuntimeException子类的自定义异常,需要在@Transaction注解上配置回滚的异常类型:

@Transactional(rollbackFor = Exception.class)

 

  6、数据源没有配置事务管理器被Spring管理,需配置如下:

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(myDataSource());
    }
}

 

  7、@Transaction注解,指定的传播机制不支持事务,如 :@Transactional(propagation = Propagation.NOT_SUPPORTED)

 

  8、未开启事务、数据库存储引擎不支持事务

 

 声明式事务配置

 一、Spring 单数据源xml事务配置

  1、定义数据源

  2、用DataSourceTransactionManager事务管理器管理数据源

  3、注册需要事务拦截的service <bean/>

  4、要应用事务的方法规则 <tx:advice/>

  5、aop切点配置,指定增强器,确保定义的事务通知在程序中的适当位置执行 <aop:config/>

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

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/> //rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"
            <!-- 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>

 

 

 二、Spring 单数据源声明式事务配置

  1、将@Transactional 注解声明在方法或类上;声明在类上,则声明类及其子类的所有方法都执行事务通知。注意:只作用于public修饰的方法。

  2、若使用JavaConfig配置,则要在其中一个有@Configuration注解的配置类上@EnableTransactionManagement

  3、在xml中使用<tx:annotation-driven/>

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

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required --> 

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- (this dependency is defined somewhere else) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- other <bean/> definitions here -->

</beans>

 

注意:

@EnableTransactionManagement and <tx:annotation-driven/> looks for @Transactional only on beans in the same application context in which they are defined. 
This means that, if you put annotation-driven configuration in a WebApplicationContext for a DispatcherServlet, it checks for @Transactional beans only in
your controllers and not your services. See MVC for more information.

@EnableTransactionManagement 注解和<tx:annotation-driven/>仅在定义和他们的相同应用程序上下文中的bean上去查找@Transactional注解。也就是说,如果将<tx:annotation-driven/>注解驱动的配置放在DispatcherServlet的WebApplicationContext中,它将仅在控制器controller中而不是service中检查@Transactional bean。

  

 三、Spring多数据源声明式事务配置

 四、Spring动态数据源AbstractRoutingDataSource声明式事务配置

 

 

 

 手动回滚事务

  有些情况下需要在事务目标方法内捕获异常,那么就需要手动回滚事务,在catch块中

 // 手动回滚
 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

   或者可以在上游捕获异常,异常回滚之后,仍会向上游抛出异常,就不用手动回滚。

 

 

 

 

二、编程式事务

 声明式事务的问题:

  1、数据库连接占用的时间过长

    事务开始则创建连接,一直到事务结束再释放连接。如果中间有其他非数据库操作,也会占用数据库连接。高并发情况下,会造成数据库连接资源浪费

    如果一个方法中的存在较多耗时操作,就容易引发长事务问题,而长事务会带来锁的竞争影响性能,同时也会导致数据库连接池被耗尽,影响程序的正常执行

  2、使用不当会导致事务失效

    如果方法存在嵌套调用,而被嵌套调用的方法也声明了@Transaction事物,就会出现事物的嵌套调用行为,容易引起事物的混乱造成程序运行结果出现异常

  3、粒度粗,最小粒度是方法,不能是代码块

    @Transaction声明式事务是将事物控制逻辑放在注解中,如果项目的复杂度增加,事务的控制可能会变得更加复杂,导致代码可读性和维护性下降

 

  

 Spring事务管理:

  Spring中对事务抽象出三个核心接口:TransactionDefinition、PlatformTransactionManager和TransactionStatus

  1、TransactionDefinition:

    存放事务的配置信息,如事务隔离级别、事务传播特性、超时、只读事务等等

  2、PlatformTransactionManager

    事务管理器,提供了与事务相关的三个操作:事务创建、事务提交、事务回滚

  3、TransactionStatus

    代表具体某一个事务的运行状态,通过它可以判断对一个事务是执行提交或者回滚操作

  简单梳理下它们三者关系:平台事务管理器真正管理事务对象,其会根据事务定义信息TransactionDefinition进行事务管理,在管理事务中产生一些状态信息会记录到TransactionStatus中

  

 编程式事务实践

  1、创建事务管理器

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
        return dataSourceTransactionManager;
    }

  2、事务操作

@Resource
    private DataSourceTransactionManager transactionManager;
    
    @Test
    public void testTransaction() {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        //1.开启一个事务
        TransactionStatus transactionStatus = transactionManager.getTransaction(def);
        try {
            // 执行sql1
            // 执行sql2
            //4.正常时进行事务提交
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            //5.出现异常后进行事务回滚
            transactionManager.rollback(transactionStatus);
        }
    }

 

  

 Spring编程式事务实践  

  Spring提供了一个专门处理事务工具类:TransactionTemplate

  1、创建TransactionTemplate实例

    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }

  2、事务操作

@Resource
    private TransactionTemplate transactionTemplate;

    @Test
    public void testTransactionTemplate() {
        transactionTemplate.executeWithoutResult(transactionStatus -> {
            try {
                //执行sql1
                //执行sql2
            } catch (Exception e) {
                //5.出现异常后进行事务回滚,注意,如果这里不进行回滚依然提交,sql1会被提交,sql2由于异常导致没有执行到
                transactionStatus.setRollbackOnly();
            }
        });
    }

  transactionTemplate通过接口回调方式,在方法中可以获取到事务运行状态信息TransactionStatus,然后通过它即可实现提交or回滚。如果需要回滚事务,只需要执行status.setRollbackOnly()即可,否则就会进行事务提交

  

  execute、executeWithoutResult

    ①、execute 带返回值

            /*
             *  执行带有返回值<Object>的事务管理
             */
            transactionTemplate.execute(new TransactionCallback<Object>() {
                @Override
                public Object doInTransaction(TransactionStatus transactionStatus) {
                    try {
                      ...
                        //.......   业务代码
                        return new Object();
                    } catch (Exception e) {
                        //回滚
                        transactionStatus.setRollbackOnly();
                        return null;
                    }
                }
            });

    ②、executeWithoutResult无返回值

      和execute的区别的地方就在于:看返回值是否有对应的结果。如果有返回值,那么就直接使用execute方法;如果没有返回值,就直接使用executeWithoutResult方法即可

            /*
             *  执行无返回值的事务管理
             */
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    try {

                        // ....  业务代码
                    } catch (Exception e) {
                        //回滚
                        transactionStatus.setRollbackOnly();
                    }
                }
            });

 

编程式事务管理

  MyBatis 的 SqlSession 提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时,你的 bean 将会注入由 Spring 管理的 SqlSession 或Mapper(映射器)。也就是说,Spring 总是为你处理了事务

  不能在 Spring 管理的 SqlSession 上调用 SqlSession.commit(),SqlSession.rollback() 或 SqlSession.close() 方法。如果这样做了,就会抛出 UnsupportedOperationException 异常。在使用注入的Mapper(映射器)时,这些方法也不会暴露出来。

  无论 JDBC 连接是否设置为自动提交,调用 SqlSession 数据方法或在 Spring 事务之外调用任何在Mapper(映射器)中方法,事务都将会自动被提交

  如果你想编程式地控制事务,请参考 the Spring reference document(Data Access -Programmatic transaction management-)

  下面的代码展示了如何使用 PlatformTransactionManager 手工管理事务:

public class TestService {

    private final PlatformTransactionManager transactionManager;

    public TestService(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    @Autowired
    private TestMapper testMapper;

    public void createUser() {
        TransactionStatus txStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            testMapper.insertUser(user);
        } catch (Exception e) {
            transactionManager.rollback(txStatus);
            throw e;
        }
        transactionManager.commit(txStatus);
    }
}

  在使用 TransactionTemplate 的时候,可以省略对 commit 和 rollback 方法的调用

public class UserService {
  private final PlatformTransactionManager transactionManager;
  public UserService(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }
  public void createUser() {
    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    transactionTemplate.execute(txStatus -> {
      userMapper.insertUser(user);
      return null;
    });
  }
}

  注意:虽然这段代码使用的是一个Mapper映射器,但换成 SqlSession 也是可以工作的。

 

 

问题

  for循环中的一个循环如何回滚?

  场景:for循环中的一个循环出现异常的话,会将所有执行过的循环全部回滚,如果只想回滚出现异常的那条循环,该怎么办?

  方案:

    情况1:for循环所在的方法有事务,在for循环之外有修改操作,for循环之外的异常也需要回滚

      ①:将for循环内的逻辑封装成一个方法,并放在一个单独的bean中

      ②:在新的bean上的此方法上加上注解:@Transactional(transactionManager = "dataSourceTransactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)

      使用 PROPAGATION_REQUIRES_NEW 传播机制

ServiceA: 

   @Autowired
    private ServiceB serviceB;

    @Transactional(transactionManager = "dataSourceTransactionManager", rollbackFor = Exception.class)
    @Override
    public void handle() {
        LOGGER.info("转码任务开始");
        // 转码模块CODE为 TRANS_CODE,查询转码模块的id
        AuditModule auditModule = auditModuleDao.queryByCode(CommonConstants.TRANS_CODE);
        if (auditModule == null) {
            return;
        }
        // 插入或者修改操作
        auditModuleDao.update(xxx);
        // 查询拿到令牌的待转码的子流程
        Integer moduleId = auditModule.getId();
        List<AuditSubFlow> auditSubFlows = auditSubFlowDao.queryToAudit(moduleId);
        for (AuditSubFlow auditSubFlow : auditSubFlows) {
            serviceB.execute(moduleId, auditSubFlow);
        }
        LOGGER.info("转码任务结束");
    }

ServiceB: @Transactional(transactionManager
= "dataSourceTransactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void execute(Integer moduleId, AuditSubFlow auditSubFlow) { ... }

     

    情况2:for循环之外之外没有修改操作,不需要回滚,只需对for循环内进行回滚

      ①:将for循环内的逻辑封装成一个方法,并放在一个单独的bean中

      ②:在新的bean上的此方法上加上注解:@Transactional(transactionManager = "dataSourceTransactionManager", rollbackFor = Exception.class) 即可

 

 

 

 

END.

 

posted @ 2020-04-02 14:51  杨岂  阅读(555)  评论(0编辑  收藏  举报