spring中@Transactional踩坑

一、背景:

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方法出现异常回滚时,消息反馈表仍然会有数据来表明这条数据发起成功与否

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
@Transactional注解用于声明一个方法或类需要在事务执行。它可以应用于方法级别或类级别。当@Transactional注解应用于类级别时,它将应用于该类的所有公共方法。 @Transactional注解具有传播属性,用于控制事务的传播行为。传播属性定义了一个方法在调用另一个带有事务的方法时如何处理事务。以下是几种常见的传播属性: 1. REQUIRED(默认):如果当前没有事务,则创建一个新的事务。如果已经存在一个事务,则加入该事务。这是最常用的传播属性。 2. REQUIRES_NEW:创建一个新的事务,并挂起当前事务(如果存在)。在新的事务执行方法,如果存在嵌套事务,则将其挂起。 3. SUPPORTS:如果当前存在事务,则加入该事务。如果没有事务,则以非事务方式执行方法。 4. NOT_SUPPORTED:以非事务方式执行方法。如果当前存在事务,则将其挂起。 5. MANDATORY:如果当前存在事务,则加入该事务。如果没有事务,则抛出异常。 6. NEVER:以非事务方式执行方法。如果当前存在事务,则抛出异常。 7. NESTED:如果当前存在事务,则在嵌套事务执行方法。如果没有事务,则创建一个新的事务。 下面是一个示例,演示了@Transactional注解的传播性: ```java @Service public class UserService { @Autowired private UserRepository userRepository; @Transactional(propagation = Propagation.REQUIRED) public void saveUser(User user) { // 保存用户信息 userRepository.save(user); // 调用另一个带有事务的方法 updateUserStatus(user.getId()); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUserStatus(Long userId) { // 更新用户状态 userRepository.updateStatus(userId); } } ``` 在上面的示例,saveUser方法使用了REQUIRED传播属性,它调用了updateUserStatus方法,该方法使用了REQUIRES_NEW传播属性。这意味着在saveUser方法开启了一个事务,如果updateUserStatus方法已经存在一个事务,则会挂起该事务并创建一个新的事务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值