准备接口

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异常,此时整体都会回滚

Java 中解决事务嵌套是怎么做到_spring boot

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()会执行成功,从而实现了事务的部分回滚.