REQUIRED、REQUIRES_NEW、NESTED
注意:以下所有的testA()
方法与testB()
方法是在两个类之中且都将bean交给spring管理,若非如此会造成事务失效问题请看第二部分
当前没有事务,则新建一个事务,如存在事务,则加入此事务1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 例子
// A.class
(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void testA() {
insertDataToTableA(dataList);
testB();
}
// B.class
(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void testB(){
insertDataToTableB(dataList);
int i = 1/0;
insertDataToTableC(dataList);
}
// 结果 testB()加入testA()的事务,testB()发生异常,两个方法均回滚
// 如果 testA()不加事务,只有testB()加,则testA()成功,testB()回滚
创建一个新事务,如果存在当前事务,则挂起该事务。即无论是否已存在事务都新建事务
1 | // 例子 |
如果当前事务存在,则在嵌套事务中执行(子事务),否则REQUIRED的操作一样(开启一个事务)
这里需要注意两点:
REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。
在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务。
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚
而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 // 例子1
// A.class
(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void testA() {
insertDataToTableA(dataList);
testB();
int i = 1/0;
}
// B.class
(rollbackFor = Exception.class,propagation = Propagation.NESTED)
public void testB(){
insertDataToTableB(dataList);
insertDataToTableC(dataList);
}
// 结果 二者皆回滚,因为是嵌套的父子级事务,所以有一个有问题就都回滚
1 | // 例子2 |
1 | // 例0 无异常 -> 两个都能顺利插入表 |
1 | // 例1 B异常 且 B的传播机制为 REQUIRES_NEW(新建事务) |
1 | // 例1 B异常 且 B的传播机制为 NESTED,在A中捕获异常 |
1 | // 例0 无异常 -> 两个都能顺利插入表 |
尽量避免内部调用,最多用A带事务调B不带事务,尤其是不能用A不带事务调B带
spring事务
@Transactional
只允许public修饰方法
final修饰导致事务失效
在同一个类中的方法直接内部调用,会导致事务失效。
没有加类似@Service
注解,将类交给spring管理
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
insertUser与doOtherThing两个方法,doOtherThing方法是在另外新建线程去执行的,就会导致两个方法不在同一个线程中执行,获取到的是不同的数据库连接,也就会导致事务失效
数据库引擎是myisam
,不支持事务
springboot会默认开启,传统spring需要配置事务管理器等
propagation参数,指定合适的传播特性
如果想要 spring 事务能够正常回滚,必须抛出它能够处理的异常(不能try,catch)。如果没有抛异常,则 spring 认为程序是正常的。
spring 事务,默认情况下只会回滚RuntimeException
(运行时异常)和Error
(错误),对于普通的 Exception(非运行时异常),它不会回滚
rollbackFor参数,指定遇到该参数指定的异常才会回滚。如果roolbackFor设置不合适,会导致发生异常不回滚
@Transactional
public void add(){
A();
B();
}
@Transactional
public void A(){
XXX
}
@Transactional
public void B(){
XXX
}
如上,B方法内发生异常时,我们希望只回滚B方法内的事务操作,但实际连A的也一起回滚了。这是因为B方法的异常抛出后没有处理,又被add方法捕获导致继续回滚。
可以在add方法中try-catch住B方法,不继续往上抛异常即可。
@Transactional加到方法上会导致整个方法都包含在事务中了,如果方法调用层级深,会导致事务非常耗时。
Transactional-声明式事务
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}
自己类中注入自己
spring ioc 内部的三级缓存保证了它不会出现循环依赖问题。