事务传播级别组合说明
例子
-
配置类
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); } }
-
事务方法
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 + '}'; } }
-
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);
-
主启动类
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。所谓挂起,就是将之前的事务的状态变为当前事务的一个属性。事务的传播级别上面已经说的很清楚了,这里想总体的区分一下。
主要分为:
- 需要创建一个新的事务。
- 需要事务,不需要创建一个新的事务。不需要用SavePoint(其实就是第二个不能为PROPAGATION_NESTED)。
- 需要事务,不需要创建一个新的事务。需要用SavePoint(其实就是第二个是PROPAGATION_NESTED)
- 不需要事务,但是可以执行
- 不能有事务,有就要报错。
下面的组合也是按照这种的思路来分析。下面只说异常的情况,正常的情况是没有可说的。
还有一点,可以指定异常回滚,要是当前的异常和指定的异常不匹配,还是会找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的操作,这就是长事务,一般默认,长事务中只有要一个报错,整个事务都会失败。
补充说明
直接看代码更加的清晰
-
做回滚(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主要是在事务做提交的时候传递的。
-
做提交(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差不多了。长事务的概念。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。