UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

背景

系统告警:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

事务提交时发现事务已被标记为回滚(rollback-only)。
复现示例:

@RestController
public class TestController {

    @Autowired
    TestService testService;

    @GetMapping("/test")
    public Object test() {
        testService.test();
        return "test success";
    }
}

@Service
public class TestService {

    @Autowired
    private DealService dealService;

    @Autowired
    private UserService userService;

    @Transactional
    public void test() {
        // 数据库更新
        userService.createUser(new UucUser(94951L));
        userService.updateUser(new UucUser(94951L));

        try {
            dealService.deal();
        } catch (Exception e) {
            System.out.println("业务异常");
            e.printStackTrace();
        }
    }
}

@Service
public class DealService {
    @Transactional
    public void deal() {
        throw new RuntimeException();
    }
}

请求接口/test会复现UnexpectedRollbackException异常。
原因是在调用TestService的事务方法test时,spring会创建一个事务,在调用DealService的事务方法deal时,spring默认传播Propagation_Required,即被调用时如存在事务则使用该事务,因为deal方法的事务和test方法的事务是同一个事务。当deal方法抛出异常后,spring会将该事务标记为回滚,但是异常被test方法捕获了,因此test方法返回时想要提交事务,由此发生了冲突!
去掉@Transactional注解或者加上@EnableAsync和@Async,该问题都不会复现。
参考资料:https://www.jianshu.com/p/f3aeda1d262a

系统源码分析

@Service
public class TenementServiceImpl implements TenementApi { 
    @Autowired
    EleBusinessLicenseApi eleBusinessLicenseApi;
    
    @Transactional
    @Override
    public ResponseVo saveCompanyTenementAndAuth {
        eleBusinessLicenseApi.notifyPlat(cubaCompanyAuthentication, cubaUser);
    }
}

@Service
@Slf4j
public class EleBusinessLicenseServiceImpl implements EleBusinessLicenseApi {

    @Autowired
    CompanyApi companyApi;
    
    @Override
    public void notifyPlat(CubaCompanyAuthentication authenticationDB, CubaUser cubaUser) {
        try {
            companyApi.asyncNotifyPlat(url);
        } catch (Exception throwable) {
            log.error("notifyPlat fail : ", throwable);
        }
    }
}

@Service
@Transactional
@Slf4j
public class CompanyApiImpl implements CompanyApi {

	@Async
	@Override
	@SneakyThrows
	public void asyncNotifyPlat( String url) {
	    HttpClientVo httpClientVo = new HttpClientVo();
	    httpClientVo.setMimeType("application/x-www-form-urlencoded");
	    httpClientVo.setUrl(url);
	    HttpResVo httpResVo = HttpClientUtils.post4Code(httpClientVo, paramMap);
	    log.info(url+"回调返回结果:"+httpResVo.toString());
	}

}

可以看出的是业务处理后想异步回调业务平台,通知业务变化,但由于通知异常导致系统报错。

原因分析

通过日志和源码,会有两个疑问:
在这里插入图片描述
即log.error("notifyPlat fail : ", throwable);为什么能打印来自asyncNotifyPlat方法抛出的异常?难道这是@Async的特性?

  1. 既然主线程catch了异常,并且方法asyncNotifyPlat不仅有@Async而且并没有事务注解,为什么会出现事务相关的问题?为什么调用一个普通方法而且捕获了异常,却导致事务问题呢?

问题1

异步压根没生效!该方法所在服务压根没开启@EnableAsync,所以@Async压根没生效!

问题2

asyncNotifyPlat所在类CompanyApiImpl 上统一标注了@Transactional;
也就是说事务是传播到了asyncNotifyPlat方法内,该方法抛出异常,导致事务被标记为回滚。

解决方案

  1. 异步
  2. 去掉事务注解
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值