SpringBoot基础之事务失效的各种原因

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

前言

事务是一个需要同时保证原子性、隔离性、一致性和持久性的一个或多个数据库操作,但是在某些情况下会存在事务失效的情况,如果你不注意,那么他就会在不经意间突然地背刺你.

事务的一些介绍可以参看前文SpringBoot基础之声明式事务和切面事务和编程式事务,包含了本文需要了解的事务的四大特征,事务的隔离级别,事务的传播行为,还有@Transactional的参数解读

事务未生效场景

事务的正常使用依靠于是事务的正确配置和使用,如果使用错误了,则事务不会生效

数据库引擎不支持事务

对于MySQL来说当前只有InnoDBNDB引擎支持事务,其中较为常用的MyISAM是不支持事务的.

如果使用了不支持事务的引擎,则事务不生效.(写这篇文章,做测试的时候我还出现了这个问题😂)

方法使用了final或者static修饰

使用了final或者static之后,不能被代理类重写,因此事务丢失.

IDEA会提示次错误,因此IDEA上少见,错误如下

1
csharp复制代码Reports the cases when your code prevents a class from being subclassed by some framework (e.g. Spring or Hibernate) at runtime.

public修饰的方法

具体原因是因为调用的AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute方法会判断,该方法是否是public方法.

IDEA会提示次错误,因此IDEA上少见,错误如下

1
csharp复制代码Reports the cases when your code prevents a class from being subclassed by some framework (e.g. Spring or Hibernate) at runtime.

非事务方法内部调用同一个类中事务方法

当无事务的A方法内部用this的方法调用带事务的B方法的时候,B事务就是失效

1
2
3
4
5
6
7
8
9
10
11
typescript复制代码@Override
public void A() {
this.B();
}

@Override
@Transactional
public void B() {
mapper.saveStudent(new Student("A"));
int zdc = 8/0;
}

直接调用B方法可以正常回滚,但是调用A方法的时候 B方法不会回滚.

回滚失败或者阻止回滚场景

事务为什么回滚,在什么情况下回滚,这些都需要参数进行定界,而大部分的参数都在@Transactional中.

在这里集中说明一下@Transactional的参数

参数 作用
value 或 transactionManager 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器.
propagation 事务的传播行为,默认值为 Propagation.REQUIRED,更详细的解释请点击
isolation 事务的隔离级别,默认值为 Isolation.DEFAULT ,更详细的解释请点击
timeout 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务.
readOnly 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为 true.
rollbackFor 或 rollbackForClassName 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型.
noRollbackFor 或 noRollbackForClassName 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型.

手动try-catch了异常

想要spring事务能够回滚,必须抛出能够捕获的异常,如果异常被try-catch了, 则程序认为没有抛出异常,则不会回滚

1
2
3
4
5
6
7
8
9
10
11
less复制代码@Override
@Transactional
public void B() {
try{
mapper.saveStudent(new Student("A"));
int zdc = 8/0;
}catch (Exception e){
e.printStackTrace();
}
}
//不会回滚

抛出的异常不属于RuntimeExceptionError异常

JDK中 @TransactionalrollbackFor参数上注释了该内容

1
2
3
4
less复制代码By default, a transaction will be rolling back on {@link RuntimeException} 
and {@link Error} but not on checked exceptions (business exceptions).
See {@link org.springframework.transaction.interceptor.
DefaultTransactionAttribute#rollbackOn(Throwable)} for a detailed explanation.

默认情况下只处理RuntimeExceptionError异常,如果你抛出其他异常则不会被处理

1
2
3
4
5
6
7
less复制代码@Override
@Transactional
public void B() throws NotFoundException {
mapper.saveStudent(new Student("A"));
throw new NotFoundException("zdc");
}
//不会回滚

因此为了能过抛出所有的异常,我们通常会在@Transactional上定义rollbackFor = Exception.class,这样能捕获所有的异常.当然你也可以赋值Exception.class的父类为Throwable.class

1
2
3
4
5
6
7
less复制代码@Override
@Transactional(rollbackFor = Exception.class)
public void B() throws NotFoundException {
mapper.saveStudent(new Student("A"));
throw new NotFoundException("zdc");
}
//会回滚

noRollbackFor定义错误

@Transactional中的noRollbackFor是 抛出指定的异常类型,不回滚事务,如果错误的设置了抛出异常则不会回滚事务

1
2
3
4
5
6
7
less复制代码 @Override
@Transactional(noRollbackFor = Exception.class)
public void B() throws NotFoundException {
mapper.saveStudent(new Student("A"));
int zdc = 1/0;
}
//不会回滚

错误的传播属性或者错误的嵌套关系

事务的传播行为,默认值为 Propagation.REQUIRED,更详细的解释请点击

本节例子 B为主方法, C子方法, 操作B的是否有事务, 操作C的传播属性 ,这个地方的情况太多,直接在下面表格中的本文中的解释部分说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
less复制代码@Service
@Slf4j
public class Transaction3ServiceImpl implements Transaction3Service {

@Autowired
private Transaction4Service transaction4Service;

@Autowired(required = false)
private StudentMapper mapper;

@Override
@Transactional(rollbackFor = Exception.class) //如果不存在事务,注释掉此行来表示
public void B(){
mapper.saveStudent(new Student("ZZZDC"));
transaction4Service.C();
// int zdc = 1/0; //如发生异常用此替代
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
less复制代码@Service
@Slf4j
public class Transaction4ServiceImpl implements Transaction4Service {

@Autowired(required = false)
private StudentMapper mapper;

@Override
@Transactional(rollbackFor = Exception.class) //传播行为会在这里操作
public void C() {
mapper.saveStudent(new Student("ZDDDC"));
//int zdc = 1/0; 如发生异常用此替代
}
}
传播行为 本文中的解释
REQUIRED 如果B存在事务,则C加入该事务(如果发生异常,则BC一起回滚);如果B不存在事务,则C创建一个新的事务(B不回滚,如果C发生异常则只有C部分回滚)
SUPPORTS 如果B存在事务,则C加入该事务(如果发生异常,则BC一起回滚);如果B不存在事务,则C以非事务的方式继续运行(BC任何情况都不回滚)
MANDATORY 如果B存在事务,则C加入该事务(如果发生异常,则一起回滚);如果B不存在事务,则C抛出异常.(C直接报错,无事务B不回滚)
REQUIRES_NEW 如果B不存在事务,C重新创建一个新的事务(无事务B发生异常不回滚,有事C发生异常则C回滚);如果B存在事务,C挂起B得事务并重新创建一个新的事务(这是两个事务,自己部分有异常,则自己部分回滚)
NOT_SUPPORTED 如果B不存在事务,C以非事务的方式运行(任何情况都不回滚);如果B存在事务,C暂停当前的事务并以非事务的方式运行(B部分报错,则B部分回滚,C不回滚;非事务C部分报错,则都不会回滚)
NEVER 如果B不存在事务,C以非事务的方式运行(任何情况都不回滚),如果B存在事务,C则抛出异常(C报错,B因为异常回滚)
NESTED 和REQUIRED效果一样.

在这个地方讲了事务的传播行为会影响事务的状态,在事务嵌套的情况下,如果某一部分报错回滚,根据情况可能全部回滚,也有可能部分回滚.

如果使用到了传播行为参数,则需要仔细分析,仔细测试,然后再交付

非SpringBoot场景

没有配置事务管理器或者配置错误

老SSM项目配置问题

SpringMVC扫描错误

老SSM项目配置问题

没有被spring管理

特殊情况特殊分析

1
2
3
4
arduino复制代码作者:ZOUZDC
链接:https://juejin.cn/post/7028963866063306760
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%