如何优雅的在事务提交/回滚前后插入业务逻辑
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会在事务提交/回滚前后调用注册的回调接口的对应方法。主要方法包括:
- suspend:在Spring开启新事务、获取Connection之前调用(未执行
registCustomer)。 - resume:开启新事务失败时调用(未执行
registCustomer)。 - flush:未调用。
- beforeCommit:事务提交前调用(已执行
registCustomer)。 - beforeCompletion:事务提交前调用,在
beforeCommit之后(已执行registCustomer)。 - afterCommit:事务提交后调用(已执行
registCustomer)。 - 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);
}
}
});
}
如果status为STATUS_COMMITTED,表示方法正常,事务已提交,需要插入成功记录;如果status为STATUS_ROLLED_BACK,则说明发生了异常,事务已回滚,则插入失败记录。完美!
提示:想了解其原理需要深入Spring事务源码实现,可参考相关技术文章。
小张再次申请领导Review代码后,领导给了小张一个大大的赞。其他同事看了代码后,都表示这是高端操作!小张也意识到,多多学习Spring相关知识,能将代码写得更优雅。
本文转自 https://zhuanlan.zhihu.com/p/375854755,如有侵权,请联系删除。
