锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. JAVA
  4. 2.3MyBatis——插件机制

2.3MyBatis——插件机制

0
  • JAVA
  • 发布于 2024-10-08
  • 11 次阅读
黄健
黄健


2.3MyBatis——插件机制

  • 1.基本用法
  • 2.原理探究
    *
  • 2.1加载过程
  • 2.2执行过程
    *
    • 2.2.1 插件的执行点
    • 2.2.2 SQL执行的几个阶段
    • 2.2.3 如何梳理出执行流程


插件机制是一款优秀框架不可或缺的组成部分,比如spring、dubbo,还有我们要聊的Mybatis等等。所谓插件,通俗一点说,就是框架提供了一个入口,允许你通过实现框架提供的扩展接口,来进行功能增强。比如spring的前置处理器允许你在Bean创建的过程中添加自定义逻辑。


具体到Mybatis框架,它的核心是ORM和SQL的映射。那么映射成功的SQL在具体执行的过程中,存在许多时机,比如参数处理阶段,SQL语句处理阶段,SQL执行阶段,返回结果的处理阶段等。在这些阶段或者说执行点,如果框架提供了扩展点,那么我们就可以通过插件在相应的环节添加一些能力。

1.基本用法

因为Mybatis的插件并不像SQL映射那样被经常使用,所以先看一下插件的基本用法。根据Mybatis官方的文档说明,只要实现Interceptor接口,然后注册插件即可。

//测试使用Springboot,插件放入Springboot容器后通过ObjectProvider注入到Configuration对象中完成注册
@Component 
@Intercepts({
    // 声明作用点,即拦截哪些方法
        @Signature(
                type = Executor.class, // 这里拦截执行器的query方法
                method = "query", 
                args = {
   MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
public class MyPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        System.out.println("自定义插件:统计运行时长");
        long begin = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();
        System.out.println((end - begin) /1000 + "s");
        return result;
    }
}

这里定义拦截器的方式类似定义切点,type的常用取值为(下文会详细介绍):

  • Executor
  • StatementHandler
  • ParameterHandler
  • ResultSetHandler

这些接口涵盖了SQL执行的各个阶段。method取值则是type接口中对应的方法,如果方法存在重载,还可以通过args进行限定。

2.原理探究

当mapper接口中的方法执行时,插件被调用并统计时长。关于Mybatis插件,使用时很自然的疑问是:它是如何被加载注册,又是如何被调用执行的(即它的设计原理是什么)?如果你没有疑问,那就现在发出疑问…
然后我们从这两点去分析和学习。

2.1加载过程

我们知道在Mybatis中,不管是以哪种方式配置Mybatis,Mybatis的配置信息和mapper.xml的数据最终都会以Configuration对象的形式存在,Configuration它是Mybatis的核心组件之一。
如果不使用Springboot,集成Mybatis需要创建相应的配置文件,在配置文件中进行Mybatis的相关配置,包括插件注册。所以猜想插件信息也是被收集到Configuration对象中。我们浅浅看一下代码:

// 1. configuration对象提供了添加拦截器的方法
public void addInterceptor(Interceptor interceptor) {

    interceptorChain.addInterceptor(interceptor);
  }
// 2 XML 配置文件解析时会调用addInterceptor,解析后通过add进行注册
pluginElement(root.evalNode("plugins"));// 解析plugins节点

// 3.1 springboot 项目中,通过ObjectProvider获取所有的Interceptor实现类,然后完成注册
public MybatisAutoConfiguration(ObjectProvider<Interceptor[]> interceptorsProvider,
      ....) {

    this.interceptors = interceptorsProvider.getIfAvailable();
    .....
  }
@Bean
@ConditionalOnMissingBean
 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

...
if (!ObjectUtils.isEmpty(this.interceptors)) {

    factory.setPlugins(this.interceptors);// 注册所有拦截器实现类
}
...
}

这里只需要明白一点,拦截器的实现类(即插件),最终都会通过addInterceptor被添加到核心组件Configuration对象的interceptorChain属性中。其他的诸如SQL映射(即mappedStatements)、转换器等都是同样的加载逻辑。

2.2执行过程

2.2Mybatis——代理与SQL映射这篇博客中,只聊到动态代理MapperProxy是如何将mapper接口与mapper.xml进行关联的。至于SQL的后续执行没有描述,下面我们通过研究插件的执行,顺带了解一下SQL的执行过程

2.2.1 插件的执行点

既然是介绍插件机制,自然是以插件的执行作为切入点。由于Mybatis的执行过程中涉及多个代理对象的调用,如果在调试过程中发现程序反复横跳,可以先不纠结,你应该至少能发现一点:在Configuration对象中,有多个方法调用了pluginAll

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 遍历执行插件逻辑
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }
  // newResultSetHandler newStatementHandler newExecutor

// pluginAll
public Object pluginAll(Object target) {

  for (Interceptor interceptor : interceptors) {

    target = interceptor.plugin(target);
  }
  return target;
}

在newResultSetHandler newStatementHandler newExecutor newParameterHandler四个方法中,都调用了pluginAll,也就是说在这些方法被调用时,都可以植入插件逻辑。

2.2.2 SQL执行的几个阶段

如果想要精确控制插件在SQL执行的哪个环节生效,就必须要知道Mybatis中SQL执行分几个阶段,并且每个阶段和newResultSetHandler newStatementHandler newExecutor newParameterHandler的对应关系。
SQL执行的先后顺序 :

  1. 创建执行器对象,对应 newExecutor()
  2. 创建SQL语句对象,对应 newStatementHandler()
  3. 参数处理器, 对应newParameterHandler()
  4. 结果集处理器,对应newParameterHandler()
    知道了各个阶段和对应的方法,这样才能精确的控制拦截器生效的时机。比如文章开头定义的插件,它将作用在newExecutor()执行时,即SQL执行的最开始阶段。

2.2.3 如何梳理出执行流程

前面提到,插件的执行流程中,由于涉及到多个代理对象的调用,所以调试流程不像普通的栈帧嵌套。调试过程如果觉得不顺,建议多去理解2.2Mybatis——代理与SQL映射这篇文章中关于JDK的代理实现。代理对象对接口方法的调用就是调用处理器中invoke方法的执行。明白了这一点,上述的插件执行点和SQL执行阶段都可以梳理出来。我们简单分析一下流程:

  1. MapperProxy确定SQL映射关系后,调用MapperMethod.execute方法,后面开始执行SQL
  2. 由于使用的是mybatis-spring-boot-starter依赖,所以这里的sqlSession是SqlSessionTemplate实例,可以看到,SqlSessionTemplate类中持有的sqlSessionProxy就是一个JDK代理对象,所以this.sqlSessionProxy.selectOne()实际是调用SqlSessionInterceptor的invoke方法
  3. 继续调试,当调用拦截器链的pluginAll()方法时,内部是调用拦截器的plugin方法。MyBatis内部有Plugin插件类,为啥自定义插件却要实现拦截器接口呢?(拦截器就是插件的逻辑部分)
  4. Plugin对象也是一个调用处理器,即上面代码中Plugin.wrap返回的是一个代理对象,在调用真实对象之前执行代理逻辑,这里的代理逻辑就是拦截器逻辑
  5. 看到这里你应该明白,方法依次返回后,pluginAll返回的是一个JDK代理对象,以executor为例,代理对象代理了executor,且代理的逻辑就是在executor执行指定方法前,调用插件逻辑(第四步invoke逻辑) ,这就是Mybatis插件的原理 。

原文链接: https://blog.csdn.net/wyy546792341/article/details/142713379

标签: #JAVA 991 #Mybatis 37
相关文章

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

SpringBoot整合异步任务执行 2024-10-08 11:24

同步任务: 同步任务是在单线程中按顺序执行,每次只有一个任务在执行,不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务,无法同时处理多个任务,响应慢,影响用 户体验 异步任务: 异步任务是在多线程中同时执行,多个任务可以并发执行,同时处理多个请求,响应快,资源

springboot kafka多数据源,通过配置动态加载发送者和消费者 2024-10-08 11:24

前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。 准备test kafka本身非常容易上手,如果我们需要单元测试,引入ja

SpringBoot 集成 Redis 2024-10-08 11:24

一:SpringBoot 集成 Redis ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list ③通过Redis客户端可以使用多种语

SpringBoot整合QQ邮箱 2024-10-08 11:24

SpringBoot可以通过导入依赖的方式集成多种技术,这当然少不了我们常用的邮箱,现在本章演示SpringBoot整合QQ邮箱发送邮件…. 下面按步骤进行: 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域,如果当前账号未开启的话自己手动开启。

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.