3236 字
7 分钟

如何优雅的在事务提交/回滚前后插入业务逻辑

文章摘要
DeepSeek R1
本文介绍如何利用Spring的TransactionSynchronizationManager在事务提交前后注册回调,优雅地实现自定义逻辑,如统计注册商户成功和失败数量。通过事务回调接口,可在不影响原始业务代码的情况下,在事务生命周期中执行额外操作,提升代码可维护性和优雅度。

Spring事务后门:优雅实现自定义逻辑

本系列文章旨在利用Spring提供的后门,优雅地实现自定义逻辑。掌握这些Spring的高阶技巧后,能迅速提升代码质量,写出更优雅的解决方案。

需求背景

某天,后台开发小张被领导交待了一个任务。

领导:小张啊,业务那边想要统计下我们这边每天注册商户成功和失败的数量,你看看怎么给他弄下这个功能。

小张心想:我弄一张商户注册记录表,在注册商户完成后,往里面插入数据不就行了吗!

于是,他马上接下了这个需求。

初始实现

5分钟后,小张建好了商户注册记录表。他打开IDE,找到商户Service的注册方法。

return前,他加上了如下代码:

// 插入成功记录
logMapper.insertSuccessRecord(customer);

完事搞定!就在小张准备提交代码时,猛然惊觉:假如方法内抛出异常(业务异常或系统异常),这种做法就不生效了!

问题发现与改进

小张马上进行了第二版修改:

try {
    doRegistCustomer(customer);
    logMapper.insertSuccessRecord(customer);
} catch (Exception e) {
    logMapper.insertFailRecord(customer);
}

小张心想:我再套一层,然后对注册方法进行try-catch,捕获到异常就插入失败记录,这总行了吧。

就在小张准备再次提交代码之时,忽然意识到了一个问题:如果插入日志的Mapper方法报错了,注册商户的事务会回滚啊!

进一步优化

遵从这个思路,小张再次调整了代码:

private void insertLog(Customer customer, boolean success) {
    try {
        if (success) {
            logMapper.insertSuccessRecord(customer);
        } else {
            logMapper.insertFailRecord(customer);
        }
    } catch (Exception e) {
        // 忽略日志插入异常,避免影响主业务
    }
}

这次,他把插入日志封装成一个方法,并在内部try-catch,这样就不会影响注册商户的逻辑了!

终于,小张提交了代码,并通知领导需求已开发完成。

代码审查与领导建议

在需求上线前,领导例行Review代码。看了小张的实现后,领导提了点意见:

领导:小张啊,虽然你这个实现没什么问题,但是搞得有点复杂。本来registCustomer方法里是注册商户的逻辑,现在被你弄到doRegistCustomer方法里了,增加了一些后期维护成本。还有,你原本应该是想要在registCustomer方法执行完后再去插入日志的吧?因为调用这个方法的地方太多,在调用方那加插入日志的逻辑改动太大了,才会想着弄个doRegistCustomer方法的吧。

小张老脸一红,心想领导不愧是领导,竟把自己的实现想法说出了个七七八八,不好意思地点了点头。

领导:其实你可以去搜下TransactionSynchronizationManager.registerSynchronization的用法。当registCustomer方法事务进行中时,可以通过这个方法插入一些业务逻辑,你换成用这个实现吧。

学习与最终实现

Review环节结束后,小张赶忙去搜索了TransactionSynchronizationManager.registerSynchronization,发现这是Spring事务提供的注册回调接口的方法。

在事务注解方法中,通过该方法注册事务回调接口后,Spring会在事务提交/回滚前后调用注册的回调接口的对应方法。主要方法包括:

  1. suspend:在Spring开启新事务、获取Connection之前调用(未执行registCustomer)。
  2. resume:开启新事务失败时调用(未执行registCustomer)。
  3. flush:未调用。
  4. beforeCommit:事务提交前调用(已执行registCustomer)。
  5. beforeCompletion:事务提交前调用,在beforeCommit之后(已执行registCustomer)。
  6. afterCommit:事务提交后调用(已执行registCustomer)。
  7. afterCompletion:事务提交后调用,在afterCommit之后(已执行registCustomer)。

在了解TransactionSynchronization的用法后,小张把代码调整为了:

@Transactional
public void registCustomer(Customer customer) {
    // 注册商户逻辑
    doRegistCustomer(customer);
    
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
        @Override
        public void afterCompletion(int status) {
            if (status == STATUS_COMMITTED) {
                logMapper.insertSuccessRecord(customer);
            } else if (status == STATUS_ROLLED_BACK) {
                logMapper.insertFailRecord(customer);
            }
        }
    });
}

如果statusSTATUS_COMMITTED,表示方法正常,事务已提交,需要插入成功记录;如果statusSTATUS_ROLLED_BACK,则说明发生了异常,事务已回滚,则插入失败记录。完美!

提示:想了解其原理需要深入Spring事务源码实现,可参考相关技术文章。

小张再次申请领导Review代码后,领导给了小张一个大大的赞。其他同事看了代码后,都表示这是高端操作!小张也意识到,多多学习Spring相关知识,能将代码写得更优雅。


本文转自 https://zhuanlan.zhihu.com/p/375854755,如有侵权,请联系删除。

Firefly
Firefly
Hello, I'm Firefly.
公告
欢迎体验 Firefly 主题复刻版,壁纸与布局已全面同步。
查看文档