聊聊关于spring 事务的一次采坑

一、前述

一直以来事务都是很多小伙伴的心病,今天我也踩了一次坑,记录分享下,加深理解

二、代码以及讲解

@Service
@Transactional
public class MiniUserServiceImpl extends ServiceImpl<MiniUserDao, MiniUser> implements MiniUserService {
    private final static Logger logger = LoggerFactory.getLogger("MINI_USER_LOGGER");

    @Resource
    private MiniUserDao miniUserDao;


    @Override
    public void add(MiniUserAddDto miniUserAddDto){
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.execute(new Runnable() {
            @Override
            public void run( ) {
                try {
                    for (int i = 0; i <3; i++) {
                        test(miniUserAddDto);
                    }
                }catch (Exception e){
                    FaithSysException.throwFaithSysException(FaithSysExceptionEnum.APP_KEY_ERROR);
                }
            }
        });
    }

    private void test(MiniUserAddDto miniUserAddDto) throws Exception{
        try {
            logger.info("添加小程序用户,请求参数:[{}]",miniUserAddDto.toString());

            MiniUser miniUser = new MiniUser();
            BeanUtil.copyProperties(miniUserAddDto,miniUser);
            miniUser.setCtime(new Date());
            miniUserDao.insert(miniUser);
            async();
            logger.info("添加小程序用户成功,插入id:[{}]",miniUser.getId());
        }catch (Exception e){
            logger.error("添加小程序用户失败,errMsg:[{}]",e.toString());
            FaithSysException.throwFaithSysException(FaithSysExceptionEnum.INTERNAL_ERROR);
        }
    }

    @Async
    public void async()throws Exception{
        Thread.sleep(2000);
        MiniUser id = miniUserDao.selectOne(new QueryWrapper<MiniUser>().eq("id", 1231));
        id.getAddress();
    }
}

1、在整个 service 上添加了 @Transactional 注解,表示式声明式事务
2、接口请求进来 add 方法,定义线程池,执行调用 test 方法,test 方法里先保存一个对象,然后异步调用 async 异步方法
3、async 方法里的 sql 查询出来时特意查询不存在的数据行,所以对象为 Null,调用 get 方法,就会报错空指针异常
4、最后查询数据的时候,发现在 test 里插入的数据行,竟然没有回滚,最后发现,
4.1、不管是线程池创建的线程,还是 new Thread 出来的线程,线程里如果抛了异常出来,在 run 方法里,都不会抛出来异常,声明式事务 @Transactional 根本感知不到有异常了,但是这个异常又会终止程序的执行,这就很头疼了
4.2、异步注解 @Async 如果标注在调用类的方法上,那么调用类调用的时候,该注解会失效,导致还是同步进行
5、最终,尝试了另外的办法,就是在方法里创建一个事务管理器,用来手动的提交事务以及回滚事务,然后创建一个新的 service ,将异步方法放到新的 service 里面,通过在原来的 service 注入新的 service 来调用异步方法,代码修改如下所示:

MiniUserServiceImpl

@Service
@Transactional
public class MiniUserServiceImpl extends ServiceImpl<MiniUserDao, MiniUser> implements MiniUserService {
    private final static Logger logger = LoggerFactory.getLogger("MINI_USER_LOGGER");

    @Resource
    private MiniUserDao miniUserDao;
    @Autowired
    private PlatformTransactionManager tx;
    @Autowired
    private Test test;

    @Override
    public void add(MiniUserAddDto miniUserAddDto){

	    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        TransactionStatus status = tx.getTransaction(def);

        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.execute(new Runnable() {
            @Override
            public void run( ) {
                try {
                    for (int i = 0; i <3; i++) {
                        test(miniUserAddDto);
                    }
                    tx.commit(status); // 提交事务
                }catch (Exception e){
                    FaithSysException.throwFaithSysException(FaithSysExceptionEnum.APP_KEY_ERROR);
                    tx.rollback(status); // 回滚事务
                }
            }
        });
    }

    private void test(MiniUserAddDto miniUserAddDto) throws Exception{
        try {
            logger.info("添加小程序用户,请求参数:[{}]",miniUserAddDto.toString());

            MiniUser miniUser = new MiniUser();
            BeanUtil.copyProperties(miniUserAddDto,miniUser);
            miniUser.setCtime(new Date());
            miniUserDao.insert(miniUser);
            test.async();
            logger.info("添加小程序用户成功,插入id:[{}]",miniUser.getId());
        }catch (Exception e){
            logger.error("添加小程序用户失败,errMsg:[{}]",e.toString());
            FaithSysException.throwFaithSysException(FaithSysExceptionEnum.INTERNAL_ERROR);
        }
    }

Test:

@Service
public class Test {
	@Resource
    private MiniUserDao miniUserDao;

    @Async
    public void async()throws Exception{
        Thread.sleep(5000);
        MiniUser id = miniUserDao.selectOne(new QueryWrapper<MiniUser>().eq("id", 1231));
        id.getAddress();
    }
}

5.1、这样,当 Test 的方法里异常的时候,事务也不会回滚,因为是异步的线程,所以事务会失效,也就不会回滚,将 @Async 去掉,事务就不会失效
5.2 、这里值得注意的是如果用下面的代码创建事务管理器,会报错空指针异常,报错行代码为:

PlatformTransactionManager txManager =  ContextLoader.getCurrentWebApplicationContext().getBean(PlatformTransactionManager.class);

用注入的方式来创建
6、还有一种可行的办法,就是使用 UncaughtExceptionHandler 来捕获未检查的异常,这里不多做说明

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值