Mybatis中拦截器的使用场景和技巧分享
作者:襄垣
场景描述
中小业务系统中,有时候会面临一些比较相似的业务场景。
- 当XXX创建成功后,需要给XXX推送一条业务消息;
- 当XXX更新的时候,需要给XXX推送一条业务提醒;
- 当XXX创建OR更新的时候,需要将最新的数据推送到搜索引擎中,以方便其他业务系统在搜索引擎中能查询到;
- 当XXX更新的时候,需要更新分布式缓存的XXX数据;
- ……
这些场景都有相似的特征,如下
特征 | 描述 |
---|---|
异步 | 实际上与主流程关系不大,可以作为附属流程异步执行 |
事务 | 大多在事务结束之后执行 |
并行 | 可以串行执行,也可以并行执行,对最终结果影响不大 |
Mybatis拦截器
Mybatis提供了一些机制,可以允许我们在做数据库操作的时候进行我们额外的一些程序。当然,这看起来并没有JPA的EntityListener好用。
但是通过这些机制,我们可以达成我们主要的目的,解耦合。(解耦合的好处显而易见,我们在关注主流程业务的时候,附属流程的业务也更容易扩展)
下面是我们即将用到的一些简单概念,如下
概念 | 描述 |
---|---|
解耦 | 解耦的本质就是将类之间的直接关系转换成间接关系 |
观察者模式 | 一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个“观察”该对象的其他对象。 |
EventBus | 基于事件驱动,观察者们监听自己感兴趣的特定事件,进行相应的处理。 |
锚定事件发生
首先,我们要找到事件发生的地方,通常来说我们更愿意在业务代码里写
Company company = new Company(); company.setCompanyName(companyName); companyMapper.insert(company); // 创建XXX成功后,触发了一些操作; doSomething(company);
但是,这种方式是耦合的,我们在doSomething() 里发生的一些异常会影响到主流程,而每一次增加附属流程的改动,都会产生影响范围的蔓延。
这里,我们使用Mybatis提供的一种方式来锚定事件发生
@Component @Intercepts( { @Signature(method = "update", type = Executor.class, args = { MappedStatement.class, Object.class }) }) public class EntityInterceptor implements Interceptor { private static final Logger LOGGER = LoggerFactory.getLogger(EntityInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { Object proceed = invocation.proceed(); try { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object params = args[1]; if (SqlCommandType.INSERT.equals(ms.getSqlCommandType())) { insertCommond(params); } if (SqlCommandType.UPDATE.equals(ms.getSqlCommandType())) { updateCommond(params); } } catch (Exception e) { LOGGER.warn("entity change warning: {}", e.getMessage()); } finally { return proceed; } } private void insertCommond(Object params) { if (Objects.isNull(params)) { return; } // 插入Company的事件 if (params instanceof Company) { Company company = (Company) params; CompanyEvent companyEvent = new CompanyEvent(CompanyEvent.TOPIC_ADD, WebUtils.getOpenId(), CompanyVo.from(company)); SpringUtil.getApplicationContext().publishEvent(companyEvent); LOGGER.info("[发布事件:{}] - [事件内容:{}]", CompanyEvent.TOPIC_ADD, JSON.toJSONString(companyEvent)); } } }
在这里,我们监听了Mybatis的Executor对象的update方法,来监听对象的新增和修改。
package org.apache.ibatis.executor; public interface Executor { int update(MappedStatement ms, Object parameter) throws SQLException; }
当 Company 的 insert 事件发生时,我们发布了一条 CompanyEvent 的事件。
订阅事件
我们使用发布-订阅模型实现了解耦合,针对刚才发布的 CompanyEvent 事件,我们来写一个事件消费者。
@Component public class CompanyListener { /** * 监听事件 - 公司. * * @param companyEvent 事件. */ @Lazy @Async @TransactionalEventListener( fallbackExecution = true, phase = TransactionPhase.AFTER_COMPLETION, classes = CompanyEvent.class) public void doAsync(CompanyEvent companyEvent) { LOGGER.info("[Company 订阅:{}] - [开始执行:{}]", companyEvent.getTopic(), JSON.toJSONString(companyVo)); } }
代码中涉及到一些注解,简单介绍下
注解 | 介绍 |
---|---|
@Component | 会注册为Spring的一个Bean |
@Lazy | 该方法会异步执行 |
@TransactionalEventListener | 在事务的不同阶段去触发执行该监听 |
我们标注了该事件是在TransactionPhase.AFTER_COMPLETION(事务提交完成)这个事务节点进行事件处理。
观察者模式
上面使用了Spring的EventListener来实现的事件驱动,除此之外,还可以使用Guava的EventBus、Vert.x的EventBus等方式实现。
很多工具类提供了关于EventBus的实现,但是使用逻辑和方式上大多大同小异。
总结
观察者模式、监听模式都有利于解耦合,根据业务诉求合理的进行开发设计,能为以后的扩展打下坚实的基础。
以上就是Mybatis中拦截器的使用场景和技巧分享的详细内容,更多关于Mybatis拦截器使用的资料请关注脚本之家其它相关文章!