前言
最近看了一下网上总结的 spring 事务生效的 N 个场景,网上列出来的场景有如下
- 数据库引擎不反对事务
- 没有被 Spring 治理
- 办法不是 public 的
- 本身调用问题
- 数据源没有配置事务管理器
- 不反对事务
- 异样被吃了
- 异样类型谬误
其中有条异样被吃了,会导致事务无奈回滚,这个引起我的好奇,是否真的是这样,刚好也没写文素材了,就来聊聊事务与异样在某些场景产生的化学反应
示例素材
1、一张没啥业务含意的表,就单纯用来演示用
CREATE TABLE `tx_test` (
`id` bigint NOT NULL AUTO_INCREMENT,
`tx_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
2、一份不按编码标准来的 service 接口
public interface TxTestService {void saveTxTestA();
void saveTxTestB();}
3、一份非必需品的单元测试
@SpringBootTest
class TransactionDemoApplicationTests {
@Autowired
private TxTestService txTestService;
@Test
void testTxA() {txTestService.saveTxTestA();
}
@Test
void testTxB() {txTestService.saveTxTestB();
}
}
注: 用的是 junit5,所以不必加上
@RunWith(SpringRunner.class)
就能够主动注入
正餐
注: 每个示例演示完,我会先做清表操作,再演示下个例子
场景一:异样被吃
1、示例一:代码如下
private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";
@Override
@Transactional(rollbackFor = Exception.class)
public void saveTxTestA() {jdbcTemplate.update(addSql, "TX-A");
try {int i = 1 % 0;} catch (Exception e) {e.printStackTrace();
}
}
问题思考:
jdbcTemplate.update(addSql, "TX-A");
这句是否是否插入数据胜利?
- 运行单元测试办法
@Test
void testTxA() {txTestService.saveTxTestA();
}
失去如下后果
答案: 是能够插入
起因:
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {cleanupTransactionInfo(txInfo);
}
这个是 spring Transaction 的局部源码,当咱们业务代码进行捕捉时,他是执行不到 completeTransactionAfterThrowing(txInfo, ex); 这个办法,这个办法外面就是执行相应的回滚操作,相干源码如下
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
2、示例代码二
@Autowired
private JdbcTemplate jdbcTemplate;
private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";
@Autowired
private TxTestServiceImpl txTestService;
@Override
@Transactional
public void saveTxTestA() {jdbcTemplate.update(addSql, "TX-A");
try {txTestService.saveTxTestC();
} catch (RuntimeException e) {e.printStackTrace();
}
}
@Transactional
public void saveTxTestC() {jdbcTemplate.update(addSql, "TX-C");
throw new RuntimeException("异样了");
}
问题思考:
jdbcTemplate.update(addSql, "TX-A");
这句是否是否插入数据胜利?
- 运行单元测试办法
@Test
void testTxA() {txTestService.saveTxTestA();
}
失去如下后果
答案: 产生了回滚,无奈插入胜利
看到这个答案,可能有些敌人会一脸懵逼,为啥上个例子把异样捕捉了,数据能够插入胜利,这次也是同样把异样捕捉,数据却无奈插入胜利
起因: 这就得从 spring 事务的流传行为说起了,spring 事务的默认流传行为是 REQUIRED。依照 REQUIRED 这个八股文的含意是 如果以后存在事务,则退出该事务,如果以后不存在事务,则创立一个新的事务
在示例中
@Transactional
public void saveTxTestC() {jdbcTemplate.update(addSql, "TX-C");
throw new RuntimeException("异样了");
}
saveTxTestC 会退出到 saveTxTestA 的事务中,即 saveTxTestC 和 saveTxTestA 是属于同一个事务,因而 saveTxTestC 抛异样回滚,依据事务的原子性,saveTxTestA 也会产生回滚
问题延长: 如果想 saveTxTestC 抛出异样了,saveTxTestA 还能插入,有没有什么解决办法
答案: 在 saveTxTestC 加上如下注解
@Transactional(propagation = Propagation.REQUIRES_NEW)
REQUIRES_NEW 它会开启一个新的事务。如果一个事务曾经存在,则先将这个存在的事务挂起
场景二:接着上一场景的延长
示例:在办法上加了 Propagation.REQUIRES_NEW 注解
@Autowired
private JdbcTemplate jdbcTemplate;
private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";
@Autowired
private TxTestServiceImpl txTestService;
@Override
@Transactional
public void saveTxTestB() {jdbcTemplate.update(addSql, "TX-B");
txTestService.saveTxTestD();}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveTxTestD() {jdbcTemplate.update(addSql, "TX-D");
throw new RuntimeException("异样了");
}
问题思考:
jdbcTemplate.update(addSql, "TX-B");
这句是否是否插入数据胜利?
- 运行单元测试办法
@Test
void testTxB() {txTestService.saveTxTestB();
}
失去如下后果
答案: 产生了回滚,无奈插入胜利
看到这个答案,可能有敌人会说,你这是在逗我吗,你方才不是说加了 REQUIRES_NEW 它会开启一个新的事务,即 saveTxTestD 和 saveTxTestB 曾经是不同事务了,saveTxTestD 回滚,关 saveTxTestB 啥事件,saveTxTestB 讲道理是要插入才对
起因: 加了 REQUIRES_NEW,saveTxTestD 和 saveTxTestB 的确是不同事务,saveTxTestD 回滚,的确影响不了 saveTxTestB。saveTxTestB 会回滚,纯正是 因为 saveTxTestD 抛出的异样,传递到了 saveTxTestB,导致 saveTxTestB 也因为 RuntimeException 产生了回滚了
问题延长: 如果想 saveTxTestD 抛出异样了,saveTxTestB 还能插入,有没有什么解决办法
答案如下:
@Override
@Transactional
public void saveTxTestB() {jdbcTemplate.update(addSql, "TX-B");
try {txTestService.saveTxTestD();
} catch (Exception e) {e.printStackTrace();
}
}
就是在 saveTxTestB 中,捕捉一下 saveTxTestD 抛出来的异样
再次运行单元测试,失去如下后果
总结
咱们在平时可能会为了面试背了一些八股文,但理论场景可能会远比这些八股文简单多,因而咱们在看这些八股文时,能够多加思考,可能会失去一些咱们平时疏忽的货色