likes
comments
collection
share

事务提交之后再执行某些操作 → 你有哪些实现方式?

作者站长头像
站长
· 阅读数 13

开心一刻

昨晚,小妹跟我妈聊天 小妹:妈,跟你商量个事,我想换车,资助我点呀 妈:哎呀,你那分扣的攒一堆都够考清华的,还换车资助点,有车开就不错了 小妹:你要是这么逼我,别说哪天我去学人家傍大款啊 妈:哎呀妈,你脸上那褶子比你人生规划都清晰,咋地,大款缺地图呀,找你? 小妹:让我回到我18岁,大个、水灵、白,你再看看 妈:你18长的像黑鱼棒似的,还水灵白,消防栓水灵,也没见谁娶它呀,女人呐,你得有内涵

事务提交之后再执行某些操作 → 你有哪些实现方式?

前情回顾

事务提交之后再执行某些操作 → 你有哪些实现方式?

事务提交之后再执行某些操作 → 你有哪些实现方式?

本着认真负责的态度,我还是提供几种实现,谁让我太宠你们了

事务拎出来

说起来很简单,做起来其实也很简单

犯病拎

为了更接近真实案例,我把

事务提交之后再执行某些操作 → 你有哪些实现方式?

调整成如下

事务提交之后再执行某些操作 → 你有哪些实现方式?

User更新插入操作日志在一个事务中, 发消息需要拎出去 拎出去还不简单,看我表演

事务提交之后再执行某些操作 → 你有哪些实现方式?

相信大家都能看懂如上代码,上游调用update的地方也不用改,简直完美!

事务提交之后再执行某些操作 → 你有哪些实现方式?

大家看仔细了,update上的@Transactional(rollbackFor = Exception.class)被拿掉了,不是漏写了! 如果update上继续保留@Transactional(rollbackFor = Exception.class)

事务提交之后再执行某些操作 → 你有哪些实现方式?

是什么情况? 那不是和没拎出来一样了吗?特么的还多写了几行代码! 回到刚拎出来的情况,updateupdateUser在同一个类中,非事务方法update调用了事务方法updateUser,事务会怎么样? 如果你还没反应过来,八股文需要再背一背了:在同一个类中,一个非事务方法调用另一个事务方法,事务不会生效 恭喜你,解决一个bug的同时,成功引入了另一个bug 你懵的同时,你老大也懵

事务提交之后再执行某些操作 → 你有哪些实现方式?

事务提交之后再执行某些操作 → 你有哪些实现方式?

别扭拎

同一个类中,非事务方法调用事务方法,事务不生效的解决方案中,是不是有这样一种解决方案自己注册自己

事务提交之后再执行某些操作 → 你有哪些实现方式?

我们debug一下,看下堆栈情况 我们先看update

事务提交之后再执行某些操作 → 你有哪些实现方式?

调用链中没有事务相关内容 我们再看updateUser

事务提交之后再执行某些操作 → 你有哪些实现方式?

调用链中有事务相关内容 从结果来看,确实能够满足要求,上游调用update的地方也不用调整,并且还自给自足,感觉是个好方案呀 但自己注册自己这种情况,你们见得多吗,甚至见过吗 反正我看着好别扭,不知道你们有这种感觉没有? 要不将就着这么用?

事务提交之后再执行某些操作 → 你有哪些实现方式?

常规拎

自己注册自己是非常不推荐的! 为什么不推荐? 来来来,把脸伸过来

事务提交之后再执行某些操作 → 你有哪些实现方式?

怎么这么多问题,非要去触及我的知识盲区? 那我就说几点: 1、违反了单一职责原则,一个类应该只负责一件事情,如果它开始依赖自己,那么它的职责就不够清晰,这可能会导致代码难以维护和扩展 2、循环依赖,自己依赖自己就是最简单版的循环依赖,虽说Spring能解决部分循环依赖,但Spring是不推荐循环依赖写法的 3、导致一些莫名其妙的问题,还非常难以排查,大家可以搜索一下,关键字类似: Spring 自己注入自己 有什么问题 推荐的做法是新建一个UserManager,类似如下

事务提交之后再执行某些操作 → 你有哪些实现方式?

此时,上游调用的地方也需要调整,改调用com.qsl.manager.UserManager#update,如下所示:

事务提交之后再执行某些操作 → 你有哪些实现方式?

同样debug下,来看看堆栈信息 com.qsl.manager.UserManager#update调用栈情况如下

事务提交之后再执行某些操作 → 你有哪些实现方式?

非常简单,没有任何的代理 我们再看下com.qsl.service.impl.UserServiceImpl#updateUser

事务提交之后再执行某些操作 → 你有哪些实现方式?

此时,调用链中是有事务相关内容的 是不是很完美的将消息发送从事务中抽出来了? 这确实也是我们最常用的方式,没有之一!

惊喜拎

既不想新增UserManager,又想把消息发送从事务中抽离出来,还要保证事务生效,并且不能用自己注册自己,有什么办法吗

事务提交之后再执行某些操作 → 你有哪些实现方式?

好处全都要,坏处往外撂,求求你,做个人吧

事务提交之后再执行某些操作 → 你有哪些实现方式?

但是,注意转折来了! 最近我还真学了一个新知识:TransactionSynchronizationManager,发现它完美契合上述的既要、又要、还要、并且要! 我们先回到最初的版本

事务提交之后再执行某些操作 → 你有哪些实现方式?

接下来看我表演,稍微调整下代码

事务提交之后再执行某些操作 → 你有哪些实现方式?

什么,调整了哪些,看的不够直观? 我真是服了你们这群老六,那我就再爱你们一次,让你们看的更直观,直接beyond compare

事务提交之后再执行某些操作 → 你有哪些实现方式?

就调整这么一点,上游调用 update 的地方也不用调整,你们的既要、又要、还要、并且要就满足了! 是不是很简单?

事务提交之后再执行某些操作 → 你有哪些实现方式?

为了严谨,我们来验证一下 如何验证呢? 最简单的办法就是在发送消息的地方打个断点,如下所示

事务提交之后再执行某些操作 → 你有哪些实现方式?

debug执行到此的时候,消息是未发送的,这个没问题吧? 那么我们只需要验证:此时事务是否已经提交 问题又来了,如何验证事务已经提交了呢? 很简单,我们直接去数据库查对应的记录,是不是修改之后的数据,如果是,那就说明事务已经提交,否则说明事务没提交,能理解吧? 我们以修改张三的密码为例,bebug未开始,此时张三的密码是zhangsan1

事务提交之后再执行某些操作 → 你有哪些实现方式?

我们把张三的密码改成zhangsan2

事务提交之后再执行某些操作 → 你有哪些实现方式?

开始bebug

事务提交之后再执行某些操作 → 你有哪些实现方式?

此时,消息还未发送,我们去数据库查下张三的密码

事务提交之后再执行某些操作 → 你有哪些实现方式?

此时张三的密码已经是zhangsan2了,是修改之后的数据,说明了什么? 说明事务已经提交了,而此时消息还未发送! 是不是很优雅的实现了最初的重点:把消息发送从事务中拎出来就好了,也就是等事务提交后,再发消息

事务提交之后再执行某些操作 → 你有哪些实现方式?

TransactionSynchronizationManager

从字面意思来看,就是一个事务同步管理器

概况

TransactionSynchronizationManagerSpring框架中提供的一个工具类,主要用于管理事务的同步操作 通过TransactionSynchronizationManager,开发者可以自定义实现TransactionSynchronization接口或继承TransactionSynchronizationAdapter

事务提交之后再执行某些操作 → 你有哪些实现方式?

从而在事务的不同阶段(如提交前、提交后、回滚后等)执行特定的操作(如发送消息) TransactionSynchronizationManager 提供了很多静态方法, registerSynchronization 就是其中之一(其他的大家自行去学习)

事务提交之后再执行某些操作 → 你有哪些实现方式?

入参类型是 TransactionSynchronization ,该接口定义了几个事务同步方法(命名很好,见名知意)

事务提交之后再执行某些操作 → 你有哪些实现方式?

分别代表着在事务的不同阶段,会被执行的操作,比如 afterCommit 会在事务提交后执行

底层原理

为什么事务提交后一定会执行 org.springframework.transaction.support.TransactionSynchronization#afterCommit ? 幕后一定有操盘手,我们来揪一揪它 怎么揪? 正所谓: 源码之下无密码 ,我们直捣黄龙干源码 问题又来了, Spring 源码那么多,我们怎么知道哪一部分跟 TransactionSynchronization 有关? 很简单,去 bebug 的堆栈中找,很容易就能找到切入点

事务提交之后再执行某些操作 → 你有哪些实现方式?

切入点是不是很明显了: org.springframework.transaction.support.AbstractPlatformTransactionManager#commit

/**
 * This implementation of commit handles participating in existing
 * transactions and programmatic rollback requests.
 * Delegates to {@code isRollbackOnly}, {@code doCommit}
 * and {@code rollback}.
 * @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
 * @see #doCommit
 * @see #rollback
 */
@Override
public final void commit(TransactionStatus status) throws TransactionException {
    if (status.isCompleted()) {
        throw new IllegalTransactionStateException(
                "Transaction is already completed - do not call commit or rollback more than once per transaction");
    }

    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    if (defStatus.isLocalRollbackOnly()) {
        if (defStatus.isDebug()) {
            logger.debug("Transactional code has requested rollback");
        }
        processRollback(defStatus, false);
        return;
    }

    if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
        if (defStatus.isDebug()) {
            logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
        }
        processRollback(defStatus, true);
        return;
    }

    processCommit(defStatus);
}

通过 commit 的源码,或者上图的调用链,我们会继续来到 org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit

/**
 * Process an actual commit.
 * Rollback-only flags have already been checked and applied.
 * @param status object representing the transaction
 * @throws TransactionException in case of commit failure
 */
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {
        boolean beforeCompletionInvoked = false;

        try {
            boolean unexpectedRollback = false;
            prepareForCommit(status);
            triggerBeforeCommit(status);
            triggerBeforeCompletion(status);
            beforeCompletionInvoked = true;

            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Releasing transaction savepoint");
                }
                unexpectedRollback = status.isGlobalRollbackOnly();
                status.releaseHeldSavepoint();
            }
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction commit");
                }
                unexpectedRollback = status.isGlobalRollbackOnly();
                doCommit(status);
            }
            else if (isFailEarlyOnGlobalRollbackOnly()) {
                unexpectedRollback = status.isGlobalRollbackOnly();
            }

            // Throw UnexpectedRollbackException if we have a global rollback-only
            // marker but still didn't get a corresponding exception from commit.
            if (unexpectedRollback) {
                throw new UnexpectedRollbackException(
                        "Transaction silently rolled back because it has been marked as rollback-only");
            }
        }
        catch (UnexpectedRollbackException ex) {
            // can only be caused by doCommit
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
            throw ex;
        }
        catch (TransactionException ex) {
            // can only be caused by doCommit
            if (isRollbackOnCommitFailure()) {
                doRollbackOnCommitException(status, ex);
            }
            else {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            }
            throw ex;
        }
        catch (RuntimeException | Error ex) {
            if (!beforeCompletionInvoked) {
                triggerBeforeCompletion(status);
            }
            doRollbackOnCommitException(status, ex);
            throw ex;
        }

        // Trigger afterCommit callbacks, with an exception thrown there
        // propagated to callers but the transaction still considered as committed.
        try {
            triggerAfterCommit(status);
        }
        finally {
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
        }

    }
    finally {
        cleanupAfterCompletion(status);
    }
}

事务提交之后再执行某些操作 → 你有哪些实现方式?

大家仔细看这个方法,在 doCommit(status) 之前有 triggerBeforeCommit(status) 、 triggerBeforeCompletion(status)  doCommit(status) 之后有 triggerAfterCommit(status) 、 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED)  这几个方法的作用很明显了吧( trigger 是触发的意思) 接下来我们跟哪个方法? 很明显,我们要跟 triggerAfterCommit(status) ,因为我们要找的是 afterCommit 的操盘手

事务提交之后再执行某些操作 → 你有哪些实现方式?

内容很简单,下一步跟的对象也很明确

事务提交之后再执行某些操作 → 你有哪些实现方式?

这里要分两步说明下 1、 TransactionSynchronizationManager.getSynchronizations()

事务提交之后再执行某些操作 → 你有哪些实现方式?

先获取所有的事务同步器,然后进行排序 排序先撇开,我们先看看获取到了哪些事务同步器

事务提交之后再执行某些操作 → 你有哪些实现方式?

第一个不眼熟,我们先不管 第二个眼不眼熟?是不是就是 com.qsl.service.impl.UserServiceImpl#update 中的匿名内部类?(如果想看的更明显,就不要用匿名内部类)

事务提交之后再执行某些操作 → 你有哪些实现方式?

是不是就对应上了:先注册,再获取,最后被调用 被调用就是下面的第 2 步 2、 invokeAfterCommit

事务提交之后再执行某些操作 → 你有哪些实现方式?

逻辑很简单,遍历所有事务同步器,逐个调用事务同步器的 afterCommit 方法 我们案例中的 发消息 就是在此处被执行了 至此,相信大家都没疑惑了吧

事务提交之后再执行某些操作 → 你有哪些实现方式?

总结

1、关于 Spring 循环依赖,大家可以翻阅下我之前的博客     Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗     再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的?     三探循环依赖 → 记一次线上偶现的循环依赖问题     四探循环依赖 → 当循环依赖遇上 BeanPostProcessor,爱情可能就产生了!     总之一句话:一定要杜绝循环依赖! 2、事务提交之后再执行某些操作的实现方式     事务失效的方式,大家一定要警惕,这坑很容易掉进去     自己注册自己的方式,直接杜绝,就当没有这种方式     Manager 方式很常规,可以使用     TransactionSynchronizationManager 方式很优雅,推荐使用     看了这篇博客后,该用哪种方式,大家心里有数了吧 3、TransactionSynchronizationManager 使用有限制条件     具体看其注释说明,就当给你们留的家庭作业了     一定要去看,不然使用出了问题可别怪我没提醒你们

转载自:https://juejin.cn/post/7347210988260130826
评论
请登录