spring事务-2(事务传播级别组合说明)

事务传播级别组合说明

例子

  1. 配置类

    package transaction;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import javax.sql.DataSource;
    
    @Configuration(proxyBeanMethods = false)
    @EnableTransactionManagement
    public class Config {
    	@Bean
    	public DriverManagerDataSource driverManagerDataSource() {
    		DriverManagerDataSource dataSource = new DriverManagerDataSource();
    		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    		dataSource.setUrl("jdbc:mysql://10.100.0.176:3306/mybatis");
    		dataSource.setUsername("root");
    		dataSource.setPassword("123456");
    		return dataSource;
    	}
    	@Bean
    	public TestTx testTx() {
    		return new TestTx();
    	}
    	@Bean
    	public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    		JdbcTemplate jdbcTemplate = new JdbcTemplate();
    		jdbcTemplate.setDataSource(dataSource);
    		return jdbcTemplate;
    	}
    
    	@Bean
    	public PlatformTransactionManager txManager(DataSource dataSource) {
    		return new DataSourceTransactionManager(dataSource);
    	}
    }
    
    
  2. 事务方法

    package transaction;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.util.Assert;
    
    public class TestTx {
    	@Autowired
    	private JdbcTemplate jdbcTemplate;
    	private int count;
    	@Autowired
    	private TestTx testTxAgent;
    
    	@Transactional(propagation = Propagation.REQUIRED)
    	public void b(int id, String name) {
    		throw new RuntimeException("updateNameWithException1");
    	}
    
    	public void be(int id, String name) {
    		jdbcTemplate.update("update t_test set name=? where id=?", name, id);
    	}
    
    	@Transactional(propagation = Propagation.REQUIRED)
    	public TestBean a(int id) {
    		TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), id);
    		Assert.notNull(testBean, "testBean not be null");
    		try {
    			testTxAgent.b(id, testBean.getName() + "-" + count++);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		be(id,"be-name");
    		return testBean;
    	}
    
    }
    
    class TestBean {
    	private Integer id;
    	private String name;
    	private Double age;
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public Double getAge() {
    		return age;
    	}
    
    	public void setAge(Double age) {
    		this.age = age;
    	}
    
    	@Override
    	public String toString() {
    		return "TestBean{" +
    				"id=" + id +
    				", name='" + name + '\'' +
    				", age=" + age +
    				'}';
    	}
    }
    
  3. DDL

    -- auto-generated definition
    create table t_student
    (
        id   int auto_increment,
        age  int         null,
        name varchar(50) null,
        constraint t_student_id_uindex
            unique (id)
    );
    
    alter table t_student
        add primary key (id);
    
    
    
  4. 主启动类

    package transaction;
    
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    import java.sql.SQLException;
    
    public class MainTest
    {
    	public static void main(String[] args) throws SQLException {
    		try {
    			AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
    			TestTx testTx = context.getBean(TestTx.class);
    			TestBean testBeanById = testTx.a(1);
    			System.out.println(testBeanById);
    		}catch (Throwable e){
    			e.printStackTrace();;
    		}
    	}
    }
    

下面的这些例子都是围绕上面的代码来测试。没有额外说明的话,都是a->b,上面方法中的@Transactional会变。不是上面例子中一成不变的。


通过上一篇文章,已经知道,最终操作的是Connection。所谓挂起,就是将之前的事务的状态变为当前事务的一个属性。事务的传播级别上面已经说的很清楚了,这里想总体的区分一下。

主要分为:

  1. 需要创建一个新的事务。
  2. 需要事务,不需要创建一个新的事务。不需要用SavePoint(其实就是第二个不能为PROPAGATION_NESTED)。
  3. 需要事务,不需要创建一个新的事务。需要用SavePoint(其实就是第二个是PROPAGATION_NESTED)
  4. 不需要事务,但是可以执行
  5. 不能有事务,有就要报错。

下面的组合也是按照这种的思路来分析。下面只说异常的情况,正常的情况是没有可说的。

还有一点,可以指定异常回滚,要是当前的异常和指定的异常不匹配,还是会找RuntimeException和Error,这里我抛出的异常都是RuntimeException的子类。所以,这里就不需要指定异常了。

在这里插入图片描述

现在有两个方法。A和B。

需要创建一个新的事务

这个要求能组合的传播级别不多。a可以为(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED),b只能为(PROPAGATION_REQUIRES_NEW)。

没有处理异常

在这里插入图片描述

b和a里面的事务都会回滚。

分析:

之前对事务的实现(TransactionInterceptor做了分析),异常会被catch掉,做事务的回滚操作,但是这个异常还会继续的抛出去。同样的逻辑,这个异常会被调用a的时候的TransactionInterceptor掉,同样的也是会做事务的回滚的。当前提条件是异常需要回滚。如果不需要回滚就会提交掉,当然需要注意的是,a方法中调用b方法之后的代码是不会执行的。如果提交也是提前调用b方法之前的操作,回滚也是。

  • 补充说明(举个例子说明一下异常不满足,导致a中的调用b方法之前的操作提交掉)

在这里插入图片描述

在这里插入图片描述

处理了异常

a在调用b的时候用try catch处理了异常。
在这里插入图片描述

b回滚,a提交

分析:

a和b是两个独立的事务,在TransactionInterceptor的catch块里面捕获了异常,在处理事务的回滚之后,会将原始的异常抛出来,在调用b之后,处理完b事务的回滚之后,会将原始的异常抛出来。但是这个时候被a在内部catch了,这个异常并没有抛到TransactionInterceptor里面的catch块。所以对于a来说,一切都是正常的。所以,他的事务会提交。
在这里插入图片描述

需要事务,不需要创建一个新的事务。不需要用SavePoint(其实就是第二个不能为PROPAGATION_NESTED)

这意思就是a得先有一个事务,b才能复用a中的事务。a(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED),b(PROPAGATION_REQUIRED)

没有处理异常

在这里插入图片描述

注意:这里从头到尾就一个事务,b用的是a的事务。

a和b的操作都没有生效,并且事务回滚。

分析:

和上面是一样的模式,TransactionInterceptor捕获了异常,做回滚操作,之后将原始的异常抛出,同样的逻辑,在a里面做了回滚操作。

需要注意的一点:

a和b用的是同一个事务,也就是同一个Connection对象,在调用b的异常之后,TransactionInterceptor里面做回滚操作,不会真正的去做回滚(也就是说不会调用Connection.rollback()方法),只是设置了一下ConnectionHolder的一个标志位(Rollback only为true),表示这个事务出现了异常,需要做回滚。真正处理回滚的地方应该是那个方法创建的Connection,它就应该做真正的回滚操作。

但是因为a调用b的时候,并没有用catch块捕获,原始的异常还是可以被TransactionInterceptor捕获到,去做回滚操作,a是真正创建connection的,所以,真正的回滚操作也得是它来。

处理了异常

在这里插入图片描述

a和b的操作都没有生效,并且事务回滚,抛出了一个怪异的错误Transaction rolled back because it has been marked as rollback-only

在这里插入图片描述

分析:

接着上面的说,b异常被a内部catch了,a对应的TransactionInterceptor无法获取到异常信息,catch块走不了,只能走提交操作,在提交的时候发现当前ConnectionHolder的Rollback属性为true,就会去做回滚操作,会真正的调用connection的rollback()方法,并且抛出UnexpectedRollbackException

长事务:就是多个方法引用的是同一个事务,并且这些方法没有savePoint的操作,这就是长事务,一般默认,长事务中只有要一个报错,整个事务都会失败。

补充说明

直接看代码更加的清晰

  1. 做回滚(AbstractPlatformTransactionManager#processRollback(DefaultTransactionStatus,boolean))

    private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    		try {
    			boolean unexpectedRollback = unexpected;
    			try {
    				triggerBeforeCompletion(status);
            // 是否有savePoint,判断的顺序是关键,如果b为nested的话,a中处理了异常是不会有问题的。因为没有设置rollback only属性
    				if (status.hasSavepoint()) {
    					if (status.isDebug()) {
    						logger.debug("Rolling back transaction to savepoint");
    					}
              // 回滚到savePoint,并且释放到savePoint
    					status.rollbackToHeldSavepoint();
    				}
            // 是否是新的事务,也就是说这个事务是否是由这个方法创建的。如
    				else if (status.isNewTransaction()) {
    					if (status.isDebug()) {
    						logger.debug("Initiating transaction rollback");
    					}
    					doRollback(status);// 这个回滚操作很简单,就是connection.rollback方法
    				}
    				else {
      			// 上述条件不满足,就是一个长事务。
    					if (status.hasTransaction()) {
                // 可以直接改TransactionStatus的对应的值,也可以直接改AbstractPlatformTransactionManager中的方法返回值。
                // 基本上来说AbstractPlatformTransactionManager为true
    						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
    							if (status.isDebug()) {
    								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
    							}
                  // 设置ConnectionHolder的rollback only
    							doSetRollbackOnly(status);
    						}
    						else {
    							if (status.isDebug()) {
    								logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
    							}
    						}
    					}
    					else {
    						logger.debug("Should roll back transaction but cannot - no transaction available");
    					}
    					// Unexpected rollback only matters here if we're asked to fail early
    					if (!isFailEarlyOnGlobalRollbackOnly()) {
    						unexpectedRollback = false;
    					}
    				}
    			}
    			catch (RuntimeException | Error ex) {
    				triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
    				throw ex;
    			}
    
    			triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
    
          // 这个为true直接抛出异常,
    			if (unexpectedRollback) {
    				throw new UnexpectedRollbackException(
    						"Transaction rolled back because it has been marked as rollback-only");
    			}
    		}
    		finally {
    			cleanupAfterCompletion(status);
    		}
    	}
    

    调用该方法的地方多着呢,unexpectedRollback的为true主要是在事务做提交的时候传递的。

  2. 做提交(AbstractPlatformTransactionManager#commit(TransactionStatus))

    public final void commit(TransactionStatus status) throws TransactionException {
    		if (status.isCompleted()) {
    			throw new IllegalTransactionStateException(
    					"Transaction is already completed - do not call commit or rollback more than once per transaction");
    		}
    
    		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
      // 先判断DefaultTransactionStatus的rollback only
    		if (defStatus.isLocalRollbackOnly()) {
    			if (defStatus.isDebug()) {
    				logger.debug("Transactional code has requested rollback");
    			}
    			processRollback(defStatus, false);
    			return;
    		}
    		// 这里传递的是true,这会判断connectionHolder中rollback only的值。
      // shouldCommitOnGlobalRollbackOnly也可以重写
    		if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
    			if (defStatus.isDebug()) {
    				logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
    			}
    			processRollback(defStatus, true);
    			return;
    		}
       // 这里才会做提交操作。
    		processCommit(defStatus);
    	}
    

对照代码来看上面的例子就比较清晰了。

需要事务,不需要创建一个新的事务。需要用SavePoint(其实就是第二个是PROPAGATION_NESTED)

这意思就是a得先有一个事务,b才能复用a中的事务。和上一个一样,但是b只能是PROPAGATION_NESTED。a(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED),b(PROPAGATION_NESTED)

没有处理异常

在这里插入图片描述

a和b的操作都没有生效,并且事务回滚。

分析

这个原因和上面的是一样,没有try catch,会导致异常直接到a相关的TransactionInterceptor里面,捕获异常做回滚。

处理了异常

在这里插入图片描述

b方法回滚,a方法执行成功。事务提交,a和b用是同一个事务。

分析:

如果存在事务,nested会按照嵌套的方式来执行,其实就是利用SavePoint,a开始一个事务,执行到b的时候。b对应的TransactionInterceptor在调用b方法之前会在当前的事务的Connection里面保存一个savePoint,在执行完成之后会释放savePoint。

如果b出现了异常,在上面的例子中已经分析了出现异常之后的回滚操作,一上来就会检查是否有savePoint,如果有会回滚到savePoint。不会设置rollback only。对这个这个例子来说,异常已经捕获到了,a对应的TransactionInterceptor没有捕获到异常,并且也不需要rollback only,所以事务正常提交。

这里a和b用的是同一个事务。这就是嵌套事务,外层事务回滚会导致内层事务也回滚,内存事务回滚不会导致外层事务回滚。这话其实不对,就没有内外层事务之说,从始到终一个事务。

不需要事务,但是可以执行

这种组合方式比较多,可以是a方法有事务,b方法没有事务,这样的组合有a(PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED),b(PROPAGATION_NOT_SUPPORTED)。也可以是a b 方法都没有事务,这样的组合有a(PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER),b(PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER)。

a方法有事务,b方法没有事务

没有处理异常

在这里插入图片描述

b中按照没有事务的方式运行,a中事务回滚

在这里插入图片描述

分析:

b按照没有事务的方式运行,所以每一行的sql操作都是事务的。所以,b中的修改成功了。

但是这个异常还是抛给了a,a所对应的TransactionInterceptor会捕获异常,做事务的回滚操作。所以,a中的修改没有作用。

处理了异常

在这里插入图片描述

b中按照没有事务的方式运行,a中事务提交

分析:

异常在a中已经处理掉了,所以a对应的TransactionInterceptor没有异常,正常提交

a b 方法都没有事务

在这里插入图片描述

a和b都按照非事务的方式来执行了,所以没什么好说的了

补充说明

既然都不需要事务了,为啥不直接去掉这两注解呢?

这两注解只是Connection相关的操作没有了,但是在Spring事务里面除了Connecton之外,还有别的,比如和事务相关的资源(TransactionSynchronization),他会走完事务操作的流程,支持在一些真正需要回滚的地方没有操作而已。

不能有事务,有就要报错。

这其实就是PROPAGATION_NEVER了,有事务就直接报错。

必须有事务,有就要报错。

这其实就是PROPAGATION_MANDATORY了,没有事务就直接报错,但是他不会创建一个新的事务,只会复用之前的事务。那这个逻辑就和a和b都是PROPAGATION_SUPPORTS差不多了。长事务的概念。


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值