多线程事务处理方案

多线程事务处理方案

1、编程式事务 + 手动回滚

1. 子任务用编程式事务执行

使用 TransactionTemplate(Spring 提供)或 PlatformTransactionManager

@Autowired
private TransactionTemplate transactionTemplate;
AtomicBoolean hasError = new AtomicBoolean(false);
List<CompletableFuture<Void>> futures = new ArrayList<>();

futures.add(CompletableFuture.runAsync(() -> {
    transactionTemplate.execute(status -> {
        try {
            // 子任务1逻辑
        } catch (Exception e) {
            hasError.set(true);
            status.setRollbackOnly();//是 Spring 编程式事务中用来标记当前事务需要回滚的方法。
        }
        return null;
    });
}));

futures.add(CompletableFuture.runAsync(() -> {
    transactionTemplate.execute(status -> {
        try {
            // 子任务2逻辑
        } catch (Exception e) {
            hasError.set(true);
            status.setRollbackOnly();
        }
        return null;
    });
}));

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

// 处理失败场景
if (hasError.get()) {
    throw new RuntimeException("子任务有失败,事务全部回滚");
}

2、❗ 注意:

  • 每个子线程中的 transactionTemplate.execute() 是独立事务;
  • 它们各自能回滚自己,但不能自动协调;
  • 所以需要你在主线程手动判断是否需要“补偿”或“告警”。

2、通过共用同一个Spring的事务的Aconnection

在这里插入图片描述

我们假设,有这么一段代码,方法a和方法b他们都开起了事务,然后在方法a当中呢,嵌套的调用了方法b,事务默认的嵌套行为,是外层a没有事务,就会开启一个事务,外层有事务呢,就会融入到

外层的事务当中,所以说上面图片中的两个方法呢,就会使用同一个事务,那这个事务的传播行为,他是如何实现的呢?

    1. 在Spring的底层,当他发现你使用了@Transactional的注解,就会为你创建一个动态代理的对象(图上标的方法a),那这个动态代理对象呢,就会开启一个事务。
    1. 那开启事务的本质呢其实就是通过JDBCconnection调用setAutoCommit(false),就相当于开启了一个事务。

在这里插入图片描述

    1. 开启事务完成之后,他会把这个connection存到Threadlocal当中(如上图)

在这里插入图片描述

为啥要存到Threadlocal当中呢?

    1. 其实就是为了你的嵌套方法,能够拿到你的外层事务的connection对象(如下图)

在这里插入图片描述

  1. 因为只有用同一个connection,才能保证用同一个事务,所以当外层事务存完connection之后呢,即第3步执行完成之后,就会执行本身的insert()方法,从而就会去执行数据库操作

完了之后就会嵌套调用方法b,那在方法b的动态代理当中呢,他就会拿到你的外层事务的这个Threadlocal当中的那个connection,从而去执行自身方法中update方法,但是方法b他发现

Threadlocal当中有值呢,他并不会提交事务,而是统一的交给外层事务进行提交,这样呢保证了事务的传播行为,那到这里应该明白了,为啥Spring的多线程事务会失效了吧?

下面我们将A调用B方法改成多线程

在这里插入图片描述

假如说方法A,是通过异步线程的方式,调用的方法b,此时方法b就无法拿到外层事务的Threadlocal当中的connection了,因为Threadlocal他是绑定在线程上面的,所以我们在A方法当中通过

new Thread()产生一个子线程之后呢,他这里再想通过Threadlocal当中去拿connection,就拿不到了。因为Threadlocal是绑定在线程上面的。(如下图)

在这里插入图片描述

此时那不到,就会开启一个事务,自己去存connectionThreadlocal当中,那这个时候方法A和方法B就会各用各的事务了(如下图)

在这里插入图片描述

实现步骤

如果我们要保证,多线程事务的一致性,可以在创建一个异步线程之后,我手动的往对应的子线程哪个Threadlocal当中呢,把外层事务的这个connection对象给他存进去

那么这样一来方法 B是不是就能够从自己的Threadlocal当中去拿到connection对象了,从而是不是就可以用同一个事务了。

那关键是外层事务的这个connection,应该这么获取呢,获取到了又该如何存到子线程Threadlocal当中去呢?

我们可以通过源码的DataSourceeTransactionmanager类中,在这个类里面有一个doBegin方法,这个方法就是Spring底层在开启事务的时候,会进行调用的。

在这里插入图片描述

在源码当中可以看到,他是通过datasoource来获取一个数据库连接,那这个datasoource,其实就是从Spring的容器当中获取到的

在这里插入图片描述

并且在下面的代码中会通过setAutocommit 来去开启一个事务

在这里插入图片描述

最后呢会把这个connection,通过这个事务同步管理器,调用bindresource方法,把它绑定到这个Threadlocal`当中

在这里插入图片描述

把它绑定到这个Threadlocal当中,并且这个Threadlocal存的是一个Map,所以我们的那个connection呢,他就会存到这个Value当中,而这个Key就是spring容器当中的datasoource

在这里插入图片描述

所以如果我们要获取connection对象的话,只需要调用事务同步管理器的getResource方法,然后把Spring容器当中的datasoource把他传进来,是不是就可以获取到了,然后我们在手动的去调用

bindresource方法,是不是就可以往Threadlocal当中,在手动的在存一遍了

在这里插入图片描述

代码

 	@Resource
    private Datasource datasource;

    @Thransactional(rollbackFor = Exception.class)
    public void a() throws InterruptedException {
        ConnectionHolder connectionHolder = (ConnectionHolder)
                TransactionSynchronizationManager.getResource(datasource);
        System.out.println("当前线程的数据库连接信息:" + connectionHolder.getTargetConnection());
        Thread thread = new Thread(() -> {
            TransactionSynchronizationManager.bindResource(datasource, connectionHolder);
            userService.b();// 调用另外一个服务的方法,该方法也会在同一个事务中执行

        });

        thread.start();

        thread.join(); // 等待线程执行完毕,确保事务完成


    }

    @Thransactional
    public void b() {
		updateById();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北执南念

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值