前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解决AOP拦截Mapper方法不知道事务是否回滚的尴尬问题

解决AOP拦截Mapper方法不知道事务是否回滚的尴尬问题

作者头像
吴就业
发布2020-08-06 09:56:57
2.3K0
发布2020-08-06 09:56:57
举报
文章被收录于专栏:Java艺术Java艺术
easymulti-datasource-spring-boot-starter

1.0.9x-RELEASE版本更新内容如下:

代码语言:javascript
复制
1、自动配置注解事务支持;2、添加事务方法出口监听器;

需求背景

最近做的一些需求遇到一个棘手的问题,例如埋点事件、AOP切面更新缓存等,可以感知事务的存在,却无法得知事务最终是回滚了还是提交了。

埋点事件:

通过Mybatis插件监听insert、update、delete三种写操作的sql语句,解析sql、分析sql操作的表、字段是否有观察者在观察(使用了观察者模式),如果有则异步回调观察者,由观察者去完成埋点事件需求。但有个弊端,事务回滚无法得知,埋点事件还是执行了。

最理想的方案应该是这样的。如果当前线程的调用链路上存在事务,那么应该在事务完成时再回调观察者,包括事务提交完成和事务回滚完成;如果当前线程的调用链路上不存在事务,则可以立即回调观察者。

AOP切面更新缓存:

在业务代码非常复杂的情况下,如果只要为了使用缓存优化查询接口的性能,去修改一大堆的接口,修改完又要重新测试,这是非常痛苦的事情,这个时候AOP就能派上用场了,无业务代码侵入当然是最好的方法。当然,也可以使用上面提到的sql监听方案。

通过在Mapper方法上添加自定义注解拦截业务相关Mapper方法的执行,方法执行完成立即更新缓存。但这样无法感知事务回滚,无法保证缓存和数据库数据的一致性。

理想的方案是这样的。如果当前调用链路上存在事务,例如,在Service层调用Mapper方法是在事务中调用的,那么应该要等到事务提交或者回滚时再确定要不要更新缓存;如果当前调用链路上不存在事务,则可以立即更新缓存。

问题:

如何将埋点事件、AOP拦截Mapper方法更新缓存的实现逻辑放在事务提交或者回滚之后再执行呢?

落地实现

easymulti-datasource-spring-boot-starter是笔者开源的一个快速整合mybatis-plus + 动态多数据源支持的starter包,不局限于主从多数据源、非主从多数据源,也支持单个数据源使用。笔者在最新的1.0.9x-RELEASE版本解决了上面提出的问题。

前提条件是项目中使用了easymulti-datasource-spring-boot-starter,由easymulti-datasource自动配置注解事务的支持。然后只需要在application配置文件中打开追踪事务方法调用链路的开关,配置如下。

代码语言:javascript
复制
## 监控事务方法调用链路
easymuti:
  transaction:
    open-chain: true

定义切面,拦截Mapper方法,在环绕方法中实现更新缓存的逻辑,代码如下。

  • TransactionInvokeContext.currentExistTransaction:判断当前调用链路上是否存在事务;
  • TransactionInvokeContext.addCurrentTransactionMethodPopListener:给当前事务绑定一个监听器(PopTransactionListener),当事务提交或者回滚时监听器被调用;

如上代码所示,首先是判断当前调用链路上是否存在事务,如果存在,则给当前事务注入一个监听器,由监听器完成缓存更新逻辑,如果不存在事务,在目标方法执行完成后且无异常抛出时执行更新缓存逻辑。

PopTransactionListener接口代码如下:

代码语言:javascript
复制
@FunctionalInterface
public interface PopTransactionListener {
    /**
     * 事务退出时回调用
     *
     * @param methodInfo 事务方法元数据信息
     */
    void onTransactionPop(TxMethodMetadata methodInfo);
}
  • methodInfo:包括反射获取的事务方法的Method、Method所属的类、事务是否抛出异常、事务注解。

拿到事务注解就可以拿到事务的隔离级别以及rollbackFor,根据方法抛出的异常就能判断当前事务是提交了还是回滚了。简单的事务回滚判断如下。

代码语言:javascript
复制
// 事务回滚简单判断
if (methodInfo.getThrowable() != null 
         && methodInfo.getTransactional() != null) {      
}

存在的不足

理解是美好的,现实是骨感的。

解决事务问题也会出现新的问题,比如,这个Mapper方法虽然是在事务方法中被调用的,但由于业务上的原因,并不需要其实现回滚,结果使用try-catch包装了Mapper方法,这种情况就凉凉了。但如果换个角度想,支持使用try-catch包装调用的Mapper显然也是对一致性要求不高的。

虽然无法实现百分百的可靠,但也并非一无是处。下个版本考虑是否需要支持通过TransactionInvokeContext获取调用链路上的所有事务方法,目前是使用双向链表存储的,想要获取并不难实现。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java艺术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档