SpringBoot中@Transactional失效场景

发布时间 2023-12-14 10:54:04作者: Jackpot_ABC

一、背景:

1、需求

  定时器需要定时到“消息通知表”中获取“消息反馈表”中不存在的数据,遍历这些数据,并对每一条数据发起流程,不管发起成功与否都需要往消息反馈表中插入一条该数据的发起结果,若发起成功还需要往“核查案件表”中插入一条该案件的主表数据

2、问题:

  发现在发起流程过程中,抛出了异常,但是事务并没有回滚,“消息反馈表”和“核查案件表”均新增了一条数据

3、service实现类代码:

@Slf4j
@Service
public class XcckServiceImpl implements XcckService {
	@Override
    public void apply() {
        List<ZfappYwxxtzCk> zfappYwxxtzCkList = 
                ywxxtzCkService.findListAndFilter(FileConstant.XCCK_DB_SOURCE, BizTypeEnum.XCCK.getCode());
        zfappYwxxtzCkList.forEach(this::applyXcck);
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    protected void applyXcck(ZfappYwxxtzCk entity) {
        try {
            HcajEntity hcajEntity = this.getEntity(entity);
            hcajService.apply(hcajEntity);
            eventBusPost(hcajEntity);
            this.saveYwxxfk(entity.getId(), SystemConstant.ENABLE_FLAG, SystemConstant.EMPTY);
        } catch (Exception ex) {
            this.saveYwxxfk(entity.getId(), SystemConstant.ZERO, ex.toString() + Arrays.toString(ex.getStackTrace()));
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    protected void saveYwxxfk(String ywId, Integer bs, String bg) {
        ywxxfkService.save(
                new YwxxfkModel()
                        .setYwxxtzbId(ywId)
                        .setFkbs(bs)
                        .setYwlx(BizTypeEnum.XCCK.getCode())
                        .setFkbg(bg));
    }
}

二、分析

1、@Transactional失效的场景:

①应用在非public修饰的方法上时,@Transactional失效

jdk动态代理(基于接口)只能访问public方法
(有个疑问,如果是CGLIB动态代理呢?)

②注解属性 propagation 设置错误时,@Transactional失效

Propagation.SUPPORTS:当前存在事务,则加入该事务;否则以非事务方式运行
Propagation.NO_SUPPORTED:以非事务方式运行,若当前存在事务则将事务挂起
Propagation.NEVER:以非事务方式运行,若当前存在事务则抛出异常

③注解属性 rollbackFor 设置错误时,@Transactional失效

默认只回滚 RuntimeException或Error

④在同一个类中,使用this调用带有@Transactional的方法时,@Transactional失效

@Transactional是通过 AOP 来进行事务的管理,调用的方法实际上是 动态代理对象 中的方法,而动态代理对象需要从容器中获取(容器启动后生成并注入动态代理对象),直接调用this调用方法的话就无法通过动态代理对象进行事务管理

⑤异常被catch且没有再次被抛出,@Transactional失效

⑥数据库引擎不支持事务,@Transactional失效

⑦使用多数据源,@Transactional失效

动态代理对象获取的数据库连接还是原来的,需要为不同的数据源配置不同的事务管理器

2、存在的问题

①@Transactional用在protected方法

②在该类中使用this调用带有@Transactional注解的方法

三、解决

1、修改代码

@Slf4j
@Service
public class XcckServiceImpl implements XcckService {

	@Autowired
	//注入自己
	private XcckServiceImpl xcckService;

	@Override
    public void apply() {
        List<ZfappYwxxtzCk> zfappYwxxtzCkList = 
                ywxxtzCkService.findListAndFilter(FileConstant.XCCK_DB_SOURCE, BizTypeEnum.XCCK.getCode());
        zfappYwxxtzCkList.forEach(xcckService::applyXcck);//调用代理对象的方法
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    //修改为public
    public void applyXcck(ZfappYwxxtzCk entity) {
        try {
            HcajEntity hcajEntity = this.getEntity(entity);
            hcajService.apply(hcajEntity);
            eventBusPost(hcajEntity);
            //调用代理对象的方法
            xcckService.saveYwxxfk(entity.getId(), SystemConstant.ENABLE_FLAG, SystemConstant.EMPTY);
        } catch (Exception ex) {
        	//调用代理对象的方法
            xcckService.saveYwxxfk(entity.getId(), SystemConstant.ZERO, ex.toString() + Arrays.toString(ex.getStackTrace()));
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    //修改为public
    public void saveYwxxfk(String ywId, Integer bs, String bg) {
        ywxxfkService.save(
                new YwxxfkModel()
                        .setYwxxtzbId(ywId)
                        .setFkbs(bs)
                        .setYwlx(BizTypeEnum.XCCK.getCode())
                        .setFkbg(bg));
    }
}

2、运行结果

当applyXcck方法出现异常回滚时,消息反馈表仍然会有数据来表明这条数据发起成功与否