前言
问题
**TransactionDefinition中的各传播行为到底是什么?**想要做个代码测试来彻底了解一下。以下是网上的一些文字描述:
PROPAGATION_REQUIREDE:如果当前存在一个事务,则加入当前事务。如果不存在任何事务,则创建一个新的事务。总之,要至少保证在一个事务中运行。PROPAGATION_REQUIRED常作为默认的事务传播行为。
PROPAGATION_SUPPORTS:如果当前存在一个事务,则加入当前事务。如果当前不存在事务,则直接执行。对于一些查询方法来说,PROPAGATION_SUPPORTS通常是比较合适的传播行为选择。如果当前方法直接执行,那么不需要事务的支持。如果当前方法被其他方法调用,而其他方法启动了一个事务,使用PROPAGATION_SUPPORTS可以保证当前方法能够加入当前事务,并洞察当前事务对数据资源所做的更新。、
PROPAGATION_MANDATORY: PROPAGATION_MANDATORY强制要求当前存在一个事务,如果不存在,则抛出异常。如果某个方法需要事务支持,但自身又不管理事务提交或者回滚,那么比较适合使用PROPAGATION_MANDATORY
PROPAGATION_REQUIRES_NEW:不管当前是否存在事务,都会创建新的事务。如果当前存在事务,会将当前的事务挂起(Suspend)。如果某个业务对象所做的事情不想影响到外层事务,PROPAGATION_REQUIRES_EW应该是合适的选择。比如,假设当前的业务方法需要向数据库中更新某些日志信息,但即使这些日志信息更新失败,我们也不想因为该业务方法的事务回演, 而影响到外层事务的成功提交。因为这种情况下,当前业务方法的事务成功与否对外层事务来说是无关紧要的。
PROPAGATION_NOT_SUPPORTE:不支持当前事务,而是在没有事务的情况下执行。如果当前存在事务的话,当前事务原则上将被挂起(Suspend),但这要看对应的PlatformTransaction- Manager实现类是否支持事务的挂起。
PROPAGATION-NEVER:永远不需要当前存在事务,如果存在当前事务,则抛出异常。
PROPAGATION_NESTED。如果存在当前事务,则在当前事务的一个嵌套事务中执行,否则与PRPAGATION_REQUIRED的行为类似,即创建新的事务,在新创建的事务中执行。
测试目的
将上述文字描述用代码的方式演绎一遍,判断与理解是否有误。由于测试繁琐,因此测试只针对部分传播行为进行。
测试传播行为:
- PROPAGATION_REQUIREDE
- PROPAGATION_REQUIRES_NEW
- PROPAGATION_NOT_SUPPORTED
- PROPAGATION-NEVER
正文
测试准备
主要准备三个事务,再嵌套的基础上查看执行后的数据库结果。每次测试完后数据库结果还原回最开始的状态。
测试代码
/**
* 测试事务的使用
*/
@Service
public class TransactionTest {
private static Logger logger = LoggerFactory.getLogger(TransactionTest.class);
private static DefaultTransactionDefinition definition;
private static DefaultTransactionDefinition definition2;
private static DefaultTransactionDefinition definition3;
private static DefaultTransactionDefinition definition4;
@Resource
BookMapper bookMapper;
@Resource(name = "transactionManager")
private DataSourceTransactionManager transactionManager;
{
definition = new DefaultTransactionDefinition();
definition2 = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
definition3 = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
definition4 = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_NEVER);
}
/**
* 开始入口
*/
public void start() throws InterruptedException {
transaction1();
}
/**
* 事务1
*/
public void transaction1() throws InterruptedException {
TransactionStatus transaction = transactionManager.getTransaction(definition);
logger.info("事务1开始执行...");
bookMapper.updateBook(new BookDo("音乐书", 6, "这是一首优美的音乐"));
transaction2();
transaction3();
transactionManager.commit(transaction);
}
/**
* 事务2
*/
public void transaction2() throws InterruptedException {
TransactionStatus transaction = transactionManager.getTransaction(definition);
logger.info("事务2开始执行...");
BookDo bookDo = bookMapper.selectBook(6);
logger.info("查询出来的book为:" + bookDo);
}
/**
* 事务3
*/
public void transaction3() {
TransactionStatus transaction = transactionManager.getTransaction(definition);
logger.info("事务3开始执行...");
bookMapper.updateBook(new BookDo("心理书", 6, "这是一本心理书籍"));
transactionManager.commit(transaction);
}
}
数据库
测试开始
PROPAGATION_REQUIREDE
由于DefaultTransactionDefinition类默认传播行为即PROPAGATION_REQUIREDE,因此使用构造方法直接创建该类。
-
结果预期:
按照文字描述,PROPAGATION_REQUIREDE行为会在事务1开启时创建一个事务,事务2和事务3由于是被事务1的方法调用,因此被嵌套进事务1当中,当commit之后,数据库中只会收到一个事务。因此事务2中select出来的结果应该是音乐书(我们姑且把查询语句也当成事务理解),而数据库中的最终修改结果应该为心理书。
-
测试截图:
- 测试结果:
符合预期。
PROPAGATION_REQUIRES_NEW
-
代码改动部分,其余保持不变:
-
结果预期:
根据文字描述,PROPAGATION_REQUIRES_NEW为每次操作创建一个新的事务,所以我们推断会有两个事务提交到数据库,但又因为PROPAGATION_REQUIRES_NEW会将外层事务挂起,因此总共应该有三个事务被提交到了数据库。由于mysql的模式事务隔离级别为可重复读,因此事务2查询出来的结果应该为地理书,事务1被挂起,事务3会等待事务1解锁后执行,由于事务3又是被嵌套在事务1的方法里的,根据子方法事务先完成原理,事务1又会等待事务3完成后才会commit。这时就会出现死锁的情况。超时后程序报错,而数据库结果未改动,仍然是地理书。
-
测试截图:
执行**SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;**查看当前数据库的事务情况。
-
测试结果:
符合预期。
PROPAGATION_NOT_SUPPORTED
- 代码变动部分,其余保持不变:
-
测试结果:
与PROPAGATION_REQUIRES_NEW中测试结果一致。
PROPAGATION-NEVER
- 代码变动部分,其余保持不变:
-
结果预期:
根据文字描述,当使用PROPAGATION-NEVER传播行为时,当执行的当前事务被另一个事务所嵌套,则抛出异常。因此程序不能得到正确的执行。
-
测试截图:
-
测试结果:
符合预期。
参考
https://www.jianshu.com/p/42c835193e92,作者:七离_82cd,来源:简书