准备接口
import java.util.List;
public interface IOperate<T> {
void add(T t);
List<T> selectALl();
}
接口实现类
@Component
public class LogdtfService implements IOperate<LOGDTF> {
@Autowired
private LOGDTFMapper logdtfMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public void add(LOGDTF logdtf) {
logdtfMapper.insert(logdtf);
}
@Override
public List<LOGDTF> selectALl() {
// TODO
return null;
}
}
@Component
public class KorekfService implements IOperate<KOREKF> {
@Autowired
private KOREKFMapper korekfMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public void add(KOREKF korekf) {
korekfMapper.insert(korekf);
// ❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗手动模拟异常❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗
int a = 1 / 0;
}
@Override
public List<KOREKF> selectALl() {
// TODO
return null;
}
}
1. 情况一 PROPAGATION_REQUIRED,常用情况
⏹logser.add()
和korser.add()
方法均被添加了事务,我们在korser.add()
中模拟了异常,由于这两个事务方法被test()
方法调用,且test()
方法也被添加了事务,也就是说logser.add()
和korser.add()
中的事务是子事务,从属于test()
中的总事务,当总事务方法或者任何一个子事务方法出现异常的时候,整体都会回滚.
public class A {
@Autowired
private LogdtfService logser;
@Autowired
private KorekfService korser;
@Transactional(rollbackFor = Exception.class)
public void test( ) {
LOGDTF logdtf = new LOGDTF();
logdtf.setPgid("jmw1");
logser.add(logdtf);
KOREKF korekf = new KOREKF();
korekf.setKorTantono(new BigDecimal(33));
korekf.setKorKaisyacd(new BigDecimal(118));
korekf.setKorTekinen(new BigDecimal(202203));
korekf.setKorKaisyanm("jmw0321");
korser.add(korekf);
}
}
2. 情况二 PROPAGATION_REQUIRED,外部捕获子事务异常
public class A {
@Autowired
private LogdtfService logser;
@Autowired
private KorekfService korser;
@Transactional(rollbackFor = Exception.class)
public void test( ) {
LOGDTF logdtf = new LOGDTF();
logdtf.setPgid("jmw1");
logser.add(logdtf);
// 捕获子事务异常
try {
KOREKF korekf = new KOREKF();
korekf.setKorTantono(new BigDecimal(33));
korekf.setKorKaisyacd(new BigDecimal(118));
korekf.setKorTekinen(new BigDecimal(202203));
korekf.setKorKaisyanm("jmw0321");
korser.add(korekf);
} catch (Exception e) {
System.out.println(e);
}
// 进行其他操作......
}
}
2.1 现象
在test()
方法中调用korser.add()
方法时,添加try...catch
,当korser.add()
出现异常时,会出现Transaction rolled back because it has been marked as rollback-only
异常,此时整体都会回滚
2.2 原因
- 两个方法都加了事务注解,并且两个方法都会受到到事务管理的拦截器增强,并且事务传播的方式都是默认的,也就是REQUIRED,当已经存在事务的时候就加入事务,没有就创建事务。这里A和B都受事务控制,并且是处于同一个事务的。
- A调用B,A中抓了B的异常,当B发生异常的时候,B的操作应该回滚,但是A吃了异常,A方法中没有产生异常,所以A的操作又应该提交,二者是相互矛盾的。
- spring的事务关联拦截器在抓到B的异常后就会标记rollback-only为true,当A执行完准备提交后,发现rollback-only为true,也会回滚,并抛出异常告诉调用者。
3. 情况三 Propagation.NESTED,事务部分回滚
korser.add()
方法使用了propagation = Propagation.NESTED
事务传播行为
@Component
public class KorekfService implements IOperate<KOREKF> {
@Autowired
private KOREKFMapper korekfMapper;
// ❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗使用Propagation.NESTED事务传播行为
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
@Override
public void add(KOREKF korekf) {
korekfMapper.insert(korekf);
// ❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗手动模拟异常❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗
int a = 1 / 0;
}
@Override
public List<KOREKF> selectALl() {
// TODO
return null;
}
}
korser.add()
方法添加了try...catch
public class A {
@Autowired
private LogdtfService logser;
@Autowired
private KorekfService korser;
@Transactional(rollbackFor = Exception.class)
public void test( ) {
LOGDTF logdtf = new LOGDTF();
logdtf.setPgid("jmw1");
logser.add(logdtf);
try {
KOREKF korekf = new KOREKF();
korekf.setKorTantono(new BigDecimal(33));
korekf.setKorKaisyacd(new BigDecimal(118));
korekf.setKorTekinen(new BigDecimal(202203));
korekf.setKorKaisyanm("jmw0321");
korser.add(korekf);
} catch (Exception e) {
System.out.println(e);
}
// 进行其他操作......
}
}
3.1 现象
- LOGDTF表插入数据成功;KOREKF插入失败,执行了回滚
- 若将
korser.add()
外部的try...catch
去掉,则整体回滚
3.2 原因
-
Propagation.NESTED
可以让事务部分回滚 - NESTED声明在被调用方法上,若调用者方法有开启事务。此时NESTED会开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务。 嵌套事务开始执行时, 它将取得一个
savepoint
。 如果这个嵌套事务失败, 我们将回滚到此savepoint
。 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。 - 当
logser.add()
方法执行完毕时,会生成一个savepoint
,此savepoint
中存储着logser.add()
的执行结果(向数据库中插入了X条数据),当korser.add()
嵌套事务的方法执行失败时,我们回滚到生成的savepoint
中,由于我们对其进行了try...catch
,因此异常并不会被抛到主方法中,所以logser.add()
会执行成功,从而实现了事务的部分回滚.