mybatis插件 executor statementhandler_mybatis源码解析(二)解析SqlSession四大对象(Executor、StatementHandler,...)...

**前文**:在mybatis源码解析(一)中我们已经对mybatis容器初始化加载配置文件和解析mapper.xml和jdk动态代理mapper接口,简单回顾下上文解析,首先是通过SqlSessionFactoryBuilder加载全局配置文件(包括SQL映射器),这些配置都会封装在Configuration中,其中每一条SQL语句的信息都会封装在MappedStatement中。然后创建SqlSession,这时还会初始化Executor执行器。最后通过调用sqlSession.getMapper()来动态代理执行Mapper中对应的SQL语句。而当一个动态代理对象进入到了MapperMethod的execute()方法后,它经过简单地判断就进入了SqlSession的delete、update、insert、select等方法,这里是真正执行SQL语句的地方。那么这些方法是如何执行呢?

答:实际上SqlSession的执行过程是通过Executor、StatementHandler、ParameterHandler和ResultSetHandler来完成数据库操作和结果返回的,它们简称为四大对象:,本篇幅我们主要讲解一下SqlSession下的四大对象。这既是sql执行的整体逻辑也是利用mybatis做插件的空隙。那么,我们还是按照惯例来介绍一个几个接口和类。

 一、介绍一下四大对象。

 - Executor:代表执行器,由它调度StatementHandler、ParameterHandler、ResultSetHandler等来执行对应的SQL,其中StatementHandler是最重要的。

 - StatementHandler:作用是使用数据库的Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的。

 - ParameterHandler:是用来处理SQL参数的。

 - ResultSetHandler:是进行数据集(ResultSet)的封装返回处理的。

 二、代码执行流程分析

1、首先来到mybatis源码解析(一)的末尾。

e4185f8369cf0d866def84e3ac681ab4.png

   可以看到这里获取了MappedStatement对象,并且调用了executor对象的query()方法来执行SQL。所以我们来看看Executor类。

   #### 2、Executor对象

   Executor表示执行器,它是真正执行Java和数据库交互的对象,所以它十分重要,每一个SqlSession都会拥有一个Executor对象,这个对象负责增删改查的具体操作,我们可以简单的将它理解为JDBC中Statement的封装版。

       Executor的关系图如下:

6473d02091387475749f5c71d2440407.png

BaseExecutor:是一个抽象类,采用模板方法的设计模式。它实现了Executor接口,实现了执行器的基本功能。

SimpleExecutor:最简单的执行器,根据对应的SQL直接执行即可,不会做一些额外的操作;拼接完SQL之后,直接交给 StatementHandler  去执行。

BatchExecutor:批处理执行器,用于将多个SQL一次性输出到数据库,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。

ReuseExecutor :可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。调用实现的四个抽象方法时会调用 prepareStatement()

CachingExecutor:启用于二级缓存时的执行器;采用静态代理;代理一个 Executor 对象。执行 update 方法前判断是否清空二级缓存;执行 query 方法前先在二级缓存中查询,命中失败再通过被代理类查询。

       我们来看看Mybatis是如何创建Executor的,其实在前面已经介绍过了,它是在Configuration类中完成的,这里不看可以跳过:

eb3e9cef018fc81e8975d1d2de70fc9d.png

Executor对象会在MyBatis加载全局配置文件时初始化,它会根据配置的类型去确定需要创建哪一种Executor,我们可以在全局配置文件settings元素中配置Executor类型,setting属性中有个defaultExecutorType,可以配置如下3个参数:

 - SIMPLE:简易执行器,它没有什么特别的,默认执行器

 - REUSE:是一种能够执行重用预处理语句的执行器

 - BATCH:执行器重用语句和批量更新,批量专用的执行器

          默认使用SimpleExecutor。而如果开启了二级缓存,则用CachingExecutor进行包装,SqlSession会调用CachingExecutor执行器的query()方法,先从二级缓存获取数据,当无法从二级缓存获取数据时,则委托给BaseExecutor的子类进行操作。

  CachingExecutor执行过程代码如下:    

9a2a9dfceb5e8bbb4fabe03797667ea2.png

在queryFromDatabase()方法中调用SimpleExecutor的 doQuery() 方法(注意:这里说是调用了SimpleExecutor的方法,但是还在BaseExecutor类中是因为SimpleExecutor继承了它,所以SimpleExecutor对象中也有这个方法,而doQuery()方法在子类SimpleExecutor实现的,所以说是调用SimpleExecutor的 doQuery() 方法。),其方法代码如下:

c9f1dd3326c63baef72a3395fa067973.png

这里显然是根据Configuration对象来构建StatementHandler,然后使用prepareStatement()方法对SQL编译和参数进行初始化。实现过程是:它调用了StatementHandler的prepare() 进行了预编译和基础的设置,然后通过StatementHandler的parameterize()来设置参数,这个parameterize()方法实际是通过ParameterHandler来对参数进行设置。最后使用 StatementHandler的query()方法,把ResultHandler传递进去,执行查询后再通过ResultSetHandler封装结果并将结果返回给调用者来完成一次查询,这样焦点又转移到了 StatementHandler 对象上。所以通过以上流程发现,MyBatis核心工作实际上是由Executor、StatementHandler、ParameterHandler和ResultSetHandler四个接口完成的,掌握这四个接口的工作原理,对理解MyBatis底层工作原理有很大帮助。

 3、StatementHandler对象 

   StatementHandler是数据库会话器,顾名思义,数据库会话器就是专门处理数据库会话的,相当于JDBC中的Statement(PreparedStatement)。StatementHandler的关系图如下:

6d8dbcf24d891f86865e58ed084d31cf.png

StatementHandler接口设计采用了适配器模式,其实现类RoutingStatementHandler根据上下文来选择适配器生成相应的StatementHandler。三个适配器分别是SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。

BaseStatementHandler: 是一个抽象类,它实现了StatementHandler接口,用于简化StatementHandler接口实现的难度,采用适配器设计模式,它主要有三个实现类SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。

 - SimpleStatementHandler:

   最简单的StatementHandler,处理不带参数运行的SQL,对应JDBC的Statement

 - PreparedStatementHandler: 预处理Statement的handler,处理带参数允许的SQL,对应JDBC的PreparedStatement(预编译处理)

 - CallableStatementHandler:存储过程的Statement的handler,处理存储过程SQL,对应JDBC的CallableStatement(存储过程处理)

 - RoutingStatementHandler:RoutingStatementHandler根据上下文来选择适配器生成相应的StatementHandler。三个适配器分别是SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。

StatementHandler的初始化过程如下(它也是在Configuration对象中完成的)

f7a42dfd8f105702c03aed3f9b071831.png

当调用到doQuery()方法时内部会通过configuration.newStatementHandler()方法来创建StatementHandler对象。

dd80e48a36369bb01a8b68c23a31dd1b.png

可以发现MyBatis生成StatementHandler代码中,创建的真实对象是一个RoutingStatementHandler的对象,而不是其它三个对象中的。但是RoutingStatementHandler并不是真实的服务对象,它是通过适配器模式来找到对应的StatementHandler来执行的。在初始化RoutingStatementHandler对象时,它会根据上下文环境来决定创建哪个具体的StatementHandler。RoutingStatementHandler 的构造方法如下

595505de26947379f7e3b20b9bcf1dfd.png

它内部定义了一个对象的适配器delegate,它是一个StatementHandler接口对象,然后构造方法根据配置来配置对应的StatementHandler对象。它的作用是给3个接口对象的使用提供一个统一且简单的适配器。此为对象的配适,可以使用对象配适器来尽可能地使用己有的类对外提供服务,可以根据需要对外屏蔽或者提供服务,甚至是加入新的服务。我们以常用的PreparedStatementHandler 为例,看看Mybatis是怎么执行查询的。

继续跟踪到SimpleExecutor对象中的prepareStatement()方法:

37de5b5613740988895bdb8222822bcb.png

可以发现Executor 执行查询时会执行 StatementHandler 的 prepare() 和 parameterize() 方法来对SQL进行预编译和参数的设置, 其中 PreparedStatementHandler 的 prepare 方法如下:

注意:这个 prepare 方法是先调用到 StatementHandler 的实现类 RoutingStatementHandler,再由RoutingStatementHandler 调用 BaseStatementHandler 中的 prepare 方法。

5ef1364960d4b798171ffef2c85b98b2.png

   通过prepare()方法,可知其中最重要的方法就是 instantiateStatement() 方法了,因为它要完成对SQL的预编译。在得到 Statement 对象的时候,会去调用 instantiateStatement() 方法,这个方法位于 BaseStatementHandler 中,是一个抽象方法由子类去实现,实际执行的是三种 StatementHandler 中的一种,我们以 PreparedStatementHandler 中的为例:

5b2ae1297b31aeacb35c9d7d462a8225.png

从上面的代码我们可以看到,instantiateStatement() 方法最终返回的也是Statement对象,所以经过一系列的调用会把创建好的 Statement 对象返回到 SimpleExecutor 简单执行器中,为后面设置参数的 parametersize 方法所用。也就是说,prepare 方法负责生成 Statement 实例对象,而 parameterize 方法用于处理 Statement 实例对应的参数。所以我们来看看parameterize 方法:

99fb8669198d3871186627c7598ad45e.png

可以看到这里通过调用了ParameterHandler对象来设置参数,所以下面我们来介绍一下ParameterHandler对象。

4、ParameterHandler对象 

   ParameterHandler 是参数处理器,它的作用是完成对预编译的参数的设置,也就是负责为 PreparedStatement 的 SQL 语句参数动态赋值。

    ParameterHandler对象的创建,ParameterHandler参数处理器对象是在创建 StatementHandler 对象的同时被创建的,同样也是由 Configuration 对象负责创建:

9ac341db8315849f6408cdf966118e4e.png

那么 ParameterHandler 如何解析SQL中的参数呢?ParameterHandler 由实现类DefaultParameterHandler执行,使用TypeHandler将参数对象类型转换成jdbcType,完成预编译SQL的参数设置,这是在setParameters()方法中完成的,setParameters()方法的实现如下:

2c0a7c344f3a264fec4c81e02fbb921a.png

2944fb3bfc9eefde5460f211f815ef47.png

至此,我们的参数就处理完成了。一切都准备就绪之后就肯定可以执行了呗!在SimpleExecutor 的 doQuery()方法中最后会调用query()方法来执行SQL语句。

可以看到这里执行了我们的SQL语句,然后对执行的结果进行处理,这里用到的是MyBatis 四大对象的最后一个神器也就是 ResultSetHandler,所以下面我们继续来介绍ResultSetHandler对象。

 5、ResultSetHandler对象

 ResultSetHandler 是结果处理器,它是用来组装结果集的。

ResultSetHandler 对象的创建,ResultSetHandler 对象是在处理查询请求时创建 StatementHandler 对象同时被创建的,同样也是由 Configuration 对象负责创建,示例如下:

 Configuration对象中的newResultSetHandler()方法:

14bea9262e952944c1c3205f9a51bdf8.png

   ResultSetHandler接口只有一个默认的实现类是DefaultResultSetHandler,我们通过SELECT语句执行得到的结果集由其 handleResultSets() 方法处理,方法如下:

55094ad91d8776b30455cd12ceb74aff.png

c5ae93eee92d6d75f4d0a3a6f2b46875.png

上面涉及的主要对象有:

ResultSetWrapper:结果集的包装器,主要针对结果集进行的一层包装。这个类中的主要属性有:

 - ResultSet : Java JDBC ResultSet接口表示数据库查询的结果。

   有关查询的文本显示了如何将查询结果作为java.sql.ResultSet返回。然后迭代此ResultSet以检查结果。

 - TypeHandlerRegistry: 类型注册器,TypeHandlerRegistry 在初始化的时候会把所有的

   Java类型和类型转换器进行注册。

 - ColumnNames: 字段的名称,也就是查询操作需要返回的字段名称

 - ClassNames: 字段的类型名称,也就是 ColumnNames 每个字段名称的类型

 - JdbcTypes: JDBC 的类型,也就是java.sql.Types 类型.

ResultMap:负责处理更复杂的映射关系。

 multipleResults:用于封装处理好的结果集。其中的主要方法是 handleResultSet。

以上在 DefaultResultSetHandler 中处理完结果映射,并把上述得到的结果返回给调用的客户端,从而执行完成一条完整的SQL语句。结果集的处理就看到这里了,因为ResultSetHandler的实现非常复杂,它涉及了CGLIB或者JAVASSIST作为延迟加载,然后通过typeHandler和ObjectFactory解析组装结果在返回,由于实际工作需要改变它的几率不高加上他比较复杂,所以这里就不在论述了,有兴趣的可自行去百度信息。

8、小结

       一条SQL语句在Mybatis中的执行过程小结:首先是创建Mapper的动态代理对象MapperProxy,然后将Mappe接口中的方法封装至MapperMethod对象,通过MapperMethod对象中的execute()方法来执行SQL,其本质是通过SqlSession下的方法来实现的,SQL语句的具体的执行则是通过SqlSession下的四大对象来完成。Executor先调用StatementHandler的prepare()方法预编译SQL,然后用parameterize()方法启用ParameterHandler设置参数,完成预编译,执行查询,update()也是这样的。如果是查询,MyBatis则会使用ResultSetHandler来封装结果并返回给调用者,从而完成查询。

## 三、拦截器讲解流程分析

1、Configuration 类包含 Mybatis 的一切配置信息,里面有 4 个非常重要的方法,也是拦截器拦截的方法

```javapublic ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);    return parameterHandler;  }  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,      ResultHandler resultHandler, BoundSql boundSql) {    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);    return resultSetHandler;  }  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);    return statementHandler;  }  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {    executorType = executorType == null ? defaultExecutorType : executorType;    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;    Executor executor;    if (ExecutorType.BATCH == executorType) {      executor = new BatchExecutor(this, transaction);    } else if (ExecutorType.REUSE == executorType) {      executor = new ReuseExecutor(this, transaction);    } else {      executor = new SimpleExecutor(this, transaction);    }    if (cacheEnabled) {      executor = new CachingExecutor(executor);    }    executor = (Executor) interceptorChain.pluginAll(executor);    return executor;  }```

整个调用过程是这样的:newExecutor -> StatementHandler -> ParameterHandler -> ResultSetHandler -> StatementHandler

说了这么多还没有讲到拦截器是怎样被执行的,别急,前面这些都是铺垫,也许有细心的小伙伴已经发现,在 Configuratin 类中的那四个方法中,都有相同的一段代码:

interceptorChain.pluginAll(...)

没错,通过名字我们也猜测得到,这是拦截器的关键,interceptorChain 是 Configuration 类的成员变量,且看 InterceptorChain.java 类:

public class InterceptorChain {  private final List interceptors = new ArrayList();  public Object pluginAll(Object target) {    for (Interceptor interceptor : interceptors) {      target = interceptor.plugin(target);    }    return target;  }  public void addInterceptor(Interceptor interceptor) {    interceptors.add(interceptor);  }    public ListgetInterceptors() {    return Collections.unmodifiableList(interceptors);  }}

在 pluginAll 方法中遍历所有拦截器的 plugin 方法,在自定义的拦截器中,我们重写 plugin 方法,这里以 通用分页拦截器 讲解调用拦截器过程,来看关键代码:

@Intercepts(    {        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),    })public class PageInterceptor implements Interceptor {    @Override    public Object plugin(Object target) {        return Plugin.wrap(target, this);    }}

Plugin.java 实现了 InvocationHandler 接口,看的出也是 Java 动态代理,调用其静态方法 wrap:

public static Object wrap(Object target, Interceptor interceptor) {    Map, Set> signatureMap = getSignatureMap(interceptor);    Class> type = target.getClass();    Class>[] interfaces = getAllInterfaces(type, signatureMap);    if (interfaces.length > 0) {      return Proxy.newProxyInstance(          type.getClassLoader(),          interfaces,          new Plugin(target, interceptor, signatureMap));    }    return target;  }

如果 interfaces.length > 0 也就会为 target 生成代理对象,也就是说为 Configuration类 四个方法调用的 executor/parameterHandler/resultSetHandler/statementHandler 生成代理对象,这里需要单独分析里面的两个很重要的方法 getSignatureMap(interceptor) 和 getAllInterfaces(type, signatureMap)

先看 getSignatureMap(interceptor) 方法:

private static Map, Set> getSignatureMap(Interceptor interceptor) {    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);    if (interceptsAnnotation == null) {      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());          }    Signature[] sigs = interceptsAnnotation.value();    Map, Set> signatureMap = new HashMap, Set>();    for (Signature sig : sigs) {      Set methods = signatureMap.get(sig.type());      if (methods == null) {        methods = new HashSet();        signatureMap.put(sig.type(), methods);      }      try {        Method method = sig.type().getMethod(sig.method(), sig.args());        methods.add(method);      } catch (NoSuchMethodException e) {        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);      }    }    return signatureMap;  }

该方法通过 Java 反射读取拦截器类上的注解信息,最终返回一个以 Type 为 key,Method 集合为 Value 的HashMap, 以上面分页拦截器为例子, key 是 org.apache.ibatis.executor.Executor, Value 是两个重载的 query 方法

再看 getAllInterfaces(type, signatureMap)

private static Class>[] getAllInterfaces(Class> type, Map, Set> signatureMap) {    Set> interfaces = new HashSet>();    while (type != null) {      for (Class> c : type.getInterfaces()) {        if (signatureMap.containsKey(c)) {          interfaces.add(c);        }      }      type = type.getSuperclass();    }    return interfaces.toArray(new Class>[interfaces.size()]);}

该方法返回根据目标实例 target 和它的父类们的接口数组,回看 Plugin.wrap 方法,如果接口数组长度大于 0,则为 target 生成代理对象

最后当在 DefaultSqlSession 中执行具体执行时,如 selectList 方法中, 此时的 executor 是刚刚生成的代理对象

return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

executor 调用的方法就会执行 Plugin 重写的 invoke 方法:

 @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    try {      Set methods = signatureMap.get(method.getDeclaringClass());      if (methods != null && methods.contains(method)) {        return interceptor.intercept(new Invocation(target, method, args));      }      return method.invoke(target, args);    } catch (Exception e) {      throw ExceptionUtil.unwrapThrowable(e);    }  }

最终,执行自定义拦截器的 intercept 方法,拦截器就是这样被执行的.

我们发现,在 Mybatis 框架中,大量的使用了 Java 动态代理,比如只需在 Mapper 接口中定义方法,并没有具体的实现类,这一切都是应用 Java 动态代理,所以理解动态代理,能更好的理解整个执行过程。

分页插件的拦截器和多租户拦截器。我们可以参考mybatis-plus的逻辑.附上官网连接。

PaginationInnerInterceptor(分页插件拦截器)

https://mp.baomidou.com/guide/page.html

TenantLineInnerInterceptor(多租户插件拦截器)

https://mp.baomidou.com/guide/interceptor-tenant-line.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值