日日行,不怕千万里
什么是 Spring 的事务传播机制?项目中遇到长事务怎么处理?相信这是大多数人面试时会被问到的高频问题,那今天就来聊聊这个问题。
事务
什么是事务?
事务这个词大家都不会陌生,甚至 ACID 背的滚瓜烂熟,被问到这个,心里一喜,心想这不是手到擒来吗?直接撞咱枪口上了。
接下来你了你的表演:
-
事务的特性
-
原子性:一个事务中的所有操作,要么全部成功,要么全部失败,没有中间状态。
-
一致性:事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态,这是事务追求的最终状态,可以说其他的三个特性都是为了保证事务的一致性。
-
隔离性:事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰,严格的隔离性对应了串行化执行,但是实际操作中很少会用到串行化的隔离级别,一般来说可以分两个方面来保证隔离性。
- A 事务写对 B 事务写的影响:通过锁来保证
- A 事务写对 B 事务读的影响:通过 MVCC 来保证
-
持久性:事务一旦提交,对数据库的影响应该是永久性的。
-
那你平时在项目中是如何使用到事务的?
哎呀,那这不又是正中下怀吗?
首先,Spring 对事务的支持建立在你所使用的数据库,如果你使用的是 mysql 数据库,恰好又是使用的 innodb 引擎,那么恭喜你,是支持事务的。那 Spring 对事物的支持一般有两种方式:
- 编程式事务管理:通过
TransactionTemplate
或者TransactionManager
手动管理事务,实际应用中很少使用,这不是本文的重点,就不在这里赘述。
public class Test1 {
@Autowired
private TransactionTemplate transactionTemplate;
public void testProgrammingTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... do something
} catch (Exception e){
//rollback
transactionStatus.setRollbackOnly();
}
}
});
}
}
```
* **声明式事务管理**:使用场景最多,也是最推荐使用的方式,直接加上 @Transactional 注解即可。
那你项目中遇到长事务是如何处理的呢,如果有一百个操作,怎么处理这种事务呢?
事务嵌套是如何处理的呢?再比如 A 事务中嵌套了 B 事务,B 事务成功开启了吗?
完了完了,你开始支支吾吾,面试官看你回答不上来,问了几个无关紧要的问题,然后心不在焉的问你有什么要问他的,你知道,凉了,要回去等通知了...
## Spring 事务传播机制
你回去仔细研究了一下发现,事务传播机制一共有七种:
`PROPAGATION_REQUIRED`:Spring 的默认传播级别 **,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行**。
`PROPAGATION_SUPPORTS`:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。
`PROPAGATION_MANDATORY`:该传播级别要求上下文中必须存在事务,否则抛出异常。
`PROPAGATION_REQUIRES_NEW`:每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。
`PROPAGATION_NOT_SUPPORTED`:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。
`PROPAGATION_NEVER`:该传播级别要求上下文中不能存在事务,否则抛出异常。
`PROPAGATION_NESTED`:嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务
哦豁,已看完这个,已经更晕了,于是你下定决心一定要搞懂,不然下次面试又得嗝屁。你去翻了翻网上大神的文章,又问了问身边的同事大佬,大佬告诉你,这七个中,你只需要记住其中两个就能应付大多数的问题,这里的大多数是大于等于 90%。
* **PROPAGATION_REQUIRED**:Spring 默认传播级别当然不用多说,那如何理解上下文中如果存在事务,就加入当前事务呢,如果不存在就新建事务执行呢?
* 如果 A 事务中如果嵌套了 B 事务,B 事务也不会是单独的,也会加入到 A 事务中。
* 如果 A 事务失败,回滚。
* 如果 A 事务中,B 事务开始前的前置操作执行成功,事务不会提交,继续执行 B 事务。
* 如果 B 事务失败,**因为 B 事务是加入了当前事务(上下文中的事务,A 事务),连同 A 事务一起回滚**
在下面代码中,a 不可以正常插入数据库,会造成回滚。原因在于使用的 Spring 默认的事务传播机制,Test1 的事务会加入到 Test 的事务中。
```java
public class Test {
@Autowired
private static Test1 test1;
@Autowired
private static AMapper aMapper;
@Transactional
public void testTransaction(A a) throws Exception {
aMapper.insert(a);
try {
test1.required();
}catch (Exception e){
System.out.println(e);
}
}
}
public class Test1 {
@Transactional
public void required() {
throw new NullPointerException("throw a test exception");
}
}
-
PROPAGATION_REQUIRES_NEW:每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务,这句话应该比较好理解了,可以理解为事务是单独的,嵌套的事务不会影响上下文中的事务。
- 如果 A 事务中嵌套了 B 事务,B 事务会是单独执行的,此过程中 A 事务会暂时挂起。
- 如果 B 事务执行成功,B 事务提交,A 事务恢复。
- 如果 B 事务执行失败,B 事务回滚,A 事务恢复。
- B 对于 A 来说是单独的,B 事务的失败不会造成 A 事务的失败,当然是在有 try-catch 的情况下。
- 如果没有 try-catch 或者说 A 事务直接执行失败了,不用想,数据是肯定会插入失败的。
就像下面的代码一样,有 try-catch,a 可以插入成功,如果没有 try-catch,a 一定会插入失败。
public class Test {
@Autowired
private static Test1 test1;
@Autowired
private static AMapper aMapper;
@Transactional
public void testTransaction(A a) throws Exception {
aMapper.insert(a);
try {
test1.required();
}catch (Exception e){
System.out.println(e);
}
}
}
```
```java
public class Test1 {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void required() {
throw new NullPointerException("throw a test exception");
}
}
其他情况
那么如果遇到其他情况,乖乖的来看你需要的是哪种 Spring 事务传播机制吧。
另:@Transactional 默认情况下,只回滚 RuntimeException 哦。
后记
你不可能真想把这玩意全背下来吧,退一万步讲,就算你全背下来了,面试官会不知道你是背的?
面试官内心 OS: 这能全知道?肯定这小子是个卷王,八股文背的这么溜,要是招进来,还不得把我也卷出去?
这些文章我有空会给大家做一个整理,到时候给大家放在一个地方,如果有疑惑或者问题,欢迎指出或者私信我哦。
同时也希望大家可以关注我的公众号【类似程序员】,会定期与大家分享我的一些想法与学习感悟,谢谢各位!
[[TagFolder/#简历面试/框架篇讲义#4. Spring 事务失效]]