# Spring事务后门:优雅实现自定义逻辑 本系列文章旨在利用Spring提供的后门,优雅地实现自定义逻辑。掌握这些Spring的高阶技巧后,能迅速提升代码质量,写出更优雅的解决方案。 ## 需求背景 某天,后台开发小张被领导交待了一个任务。 **领导**:小张啊,业务那边想要统计下我们这边每天注册商户成功和失败的数量,你看看怎么给他弄下这个功能。 小张心想:我弄一张商户注册记录表,在注册商户完成后,往里面插入数据不就行了吗!  于是,他马上接下了这个需求。 ## 初始实现 5分钟后,小张建好了商户注册记录表。他打开IDE,找到商户Service的注册方法。  在`return`前,他加上了如下代码: ```java // 插入成功记录 logMapper.insertSuccessRecord(customer); ``` 完事搞定!就在小张准备提交代码时,猛然惊觉:假如方法内抛出异常(业务异常或系统异常),这种做法就不生效了! ## 问题发现与改进 小张马上进行了第二版修改: ```java try { doRegistCustomer(customer); logMapper.insertSuccessRecord(customer); } catch (Exception e) { logMapper.insertFailRecord(customer); } ``` 小张心想:我再套一层,然后对注册方法进行`try-catch`,捕获到异常就插入失败记录,这总行了吧。 就在小张准备再次提交代码之时,忽然意识到了一个问题:如果插入日志的Mapper方法报错了,注册商户的事务会回滚啊! ## 进一步优化 遵从这个思路,小张再次调整了代码: ```java 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`的用法后,小张把代码调整为了: ```java @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](https://zhuanlan.zhihu.com/p/375854755),如有侵权,请联系删除。
评论