美文网首页Mybatis
MyBatis源码最后一天

MyBatis源码最后一天

作者: Curtain_call | 来源:发表于2023-01-12 10:43 被阅读0次

    一、MyBatis插件原理

    1.MyBatis插件介绍

    MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?

    我们进入官网看一看:

    MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

    Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

    ParameterHandler (getParameterObject, setParameters)

    ResultSetHandler (handleResultSets, handleOutputParameters)

    StatementHandler (prepare, parameterize, batch, update, query)

    我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。

    总体概括为:

    1)拦截执行器的方法

    2)拦截参数的处理

    3)拦截结果集的处理

    4)拦截Sql语法构建的处理

    2.拦截器介绍及配置

    首先我们看下MyBatis拦截器的接口定义:

    比较简单,只有3个方法。 MyBatis默认没有一个拦截器接口的实现类,开发者们可以实现符合自己需求的拦截器。

    下面的MyBatis官网的一个拦截器实例:

    全局xml配置:

    这个拦截器拦截Executor接口的update方法(其实也就是SqlSession的新增,删除,修改操作),所有执行executor的update方法都会被该拦截器拦截到。

    3.源码分析

    下面我们分析一下这段代码背后的源码。

    首先从源头->配置文件开始分析,XMLConfigBuilder解析MyBatis全局配置文件的pluginElement私有方法:

    具体的解析代码其实比较简单,就不贴了,主要就是通过反射实例化plugin节点中的interceptor属性表示的类。然后调用全局配置类Configuration的addInterceptor方法。

    这个interceptorChain是Configuration的内部属性,类型为InterceptorChain,也就是一个拦截器链,我们来看下它的定义:

    现在我们理解了拦截器配置的解析以及拦截器的归属,现在我们回过头看下为何拦截器会拦截这些方法(Executor,ParameterHandler,ResultSetHandler,StatementHandler的部分方法):

    以上4个方法都是Configuration的方法。这些方法在MyBatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是Executor,ParameterHandler,ResultSetHandler,StatementHandler(其中ParameterHandler和ResultSetHandler的创建是在创建StatementHandler[3个可用的实现类CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler]的时候,其构造函数调用的[这3个实现类的构造函数其实都调用了父类BaseStatementHandler的构造函数])。

    这4个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法,InterceptorChain的pluginAll刚才已经介绍过了,就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。注意:拦截器的plugin方法的返回值会直接被赋值给原先的对象。

    由于可以拦截StatementHandler,这个接口主要处理sql语法的构建,因此比如分页的功能,可以用拦截器实现,只需要在拦截器的plugin方法中处理StatementHandler接口实现类中的sql即可,可使用反射实现。

    MyBatis还提供了 @Intercepts和 @Signature关于拦截器的注解。官网的例子就是使用了这2个注解,还包括了Plugin类的使用:

    下面我们就分析这3个 "新组合" 的源码,首先先看Plugin类的wrap方法:

    Plugin类实现了InvocationHandler接口,很明显,我们看到这里返回了一个JDK自身提供的动态代理类。我们解剖一下这个方法调用的其他方法,getSignatureMap方法:

    getSignatureMap方法解释:首先会拿到拦截器这个类的 @Interceptors注解,然后拿到这个注解的属性 @Signature注解集合,然后遍历这个集合,遍历的时候拿出 @Signature注解的type属性(Class类型),然后根据这个type得到带有method属性和args属性的Method。由于 @Interceptors注解的 @Signature属性是一个属性,所以最终会返回一个以type为key,value为Set的Map。

    比如这个 @Interceptors注解会返回一个key为Executor,value为集合(这个集合只有一个元素,也就是Method实例,这个Method实例就是Executor接口的update方法,且这个方法带有MappedStatement和Object类型的参数)。这个Method实例是根据 @Signature的method和args属性得到的。如果args参数跟type类型的method方法对应不上,那么将会抛出异常。

    getAllInterfaces方法:

    getAllInterfaces方法解释:根据目标实例target(这个target就是之前所说的MyBatis拦截器可以拦截的类,Executor,ParameterHandler,ResultSetHandler,StatementHandler)和它的父类们,返回signatureMap中含有target实现的接口数组。

    所以Plugin这个类的作用就是根据 @Interceptors注解,得到这个注解的属性 @Signature数组,然后根据每个 @Signature注解的type,method,args属性使用反射找到对应的Method。最终根据调用的target对象实现的接口决定是否返回一个代理对象替代原先的target对象。

    比如MyBatis官网的例子,当Configuration调用newExecutor方法的时候,由于Executor接口的update(MappedStatement ms, Object parameter)方法被拦截器被截获。因此最终返回的是一个代理类Plugin,而不是Executor。这样调用方法的时候,如果是个代理类,那么会执行:

    没错,如果找到对应的方法被代理之后,那么会执行Interceptor接口的interceptor方法。

    这个Invocation类如下:

    它的proceed方法也就是调用原先方法(不走代理)。

    4.总结

    MyBatis拦截器接口提供的3个方法中,plugin方法用于某些处理器(Handler)的构建过程。interceptor方法用于处理代理类的执行。setProperties方法用于拦截器属性的设置。

    其实MyBatis官网提供的使用 @Interceptors和 @Signature注解以及Plugin类这样处理拦截器的方法,我们不一定要直接这样使用。我们也可以抛弃这3个类,直接在plugin方法内部根据target实例的类型做相应的操作。

    总体来说MyBatis拦截器还是很简单的,拦截器本身不需要太多的知识点,但是学习拦截器需要对MyBatis中的各个接口很熟悉,因为拦截器涉及到了各个接口的知识点。


    二、MyBatis执行Sql的流程分析

    本章着重介绍MyBatis执行Sql的流程,关于在执行过程中缓存、动态SQl生成等细节不在本章中体现。

    1.事例

    还是以之前的查询作为列子:

    之前提到拿到sqlSession之后就能进行各种CRUD操作了,所以我们就从sqlSession.getMapper这个方法开始分析,看下整个Sql的执行流程是怎么样的。

    2.openSession的过程

    Executor分成两大类,一类是CacheExecutor,另一类是普通Executor。

    普通Executor又分为三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

    1)SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

    2)ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。

    3)BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

    作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

    CacheExecutor其实是封装了普通的Executor,和普通的区别是在查询前先会查询缓存中是否存在结果,如果存在就使用缓存中的结果,如果不存在还是使用普通的Executor进行查询,再将查询出来的结果存入缓存。

    到此为止,我们已经获得了SqlSession,拿到SqlSession就可以执行各种CRUD方法了。

    3.总结

    3.1简单总结

    1)拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor),这个Sql执行器会代理你配置的拦截器方法。

    2)获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象,所以通过SqlSession也能拿到全局配置。

    3)获得SqlSession对象后就能执行各种CRUD方法了。

    3.2一些重要类总结

    1)SqlSessionFactory

    2)SqlSessionFactoryBuilder

    3)SqlSession(默认使用DefaultSqlSession)

    4)Executor接口

    5)Plugin、InterceptorChain的pluginAll方法

    4.获取Mapper的流程

    进入sqlSession.getMapper方法,会发现调的是Configration对象的getMapper方法:

    进入getMapper方法:

    进入MapperProxyFactory的newInstance方法:

    获取Mapper的流程总结如下:

    Mapper方法的执行流程:

    下面是动态代理类MapperProxy,调用Mapper接口的所有方法都会先调用到这个代理类的invoke方法(注意由于Mybatis中的Mapper接口没有实现类,所以MapperProxy这个代理对象中没有委托类,也就是说MapperProxy干了代理类和委托类的事情)。好了下面重点看下invoke方法。

    MapperProxy的invoke方法非常简单,主要干的工作就是创建MapperMethod对象或者是从缓存中获取MapperMethod对象。获取到这个对象后执行execute方法。

    所以这边需要进入MapperMethod的execute方法:这个方法判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作。(这边以sqlSession.selectOne这种方式进行分析~)

    sqlSession.selectOne方法会会调到DefaultSqlSession的selectList方法。这个方法获取了获取了MappedStatement对象,并最终调用了Executor的query方法。

    然后,通过一层一层的调用(这边省略了缓存操作的环节,会在后面的文章中介绍),最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:

    接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的:

    到此,整个调用流程结束。

    5.简单总结

    这边结合获取SqlSession的流程,做下简单的总结:

    1)SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象。

    2)拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor组件中包含了Transaction对象),这个Sql执行器会代理你配置的拦截器方法。

    3)获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象和上面创建的Executor对象,所以通过SqlSession也能拿到全局配置;

    4)获得SqlSession对象后就能执行各种CRUD方法了。


    以上是获得SqlSession的流程,下面总结下本博客中介绍的Sql的执行流程:

    1)调用SqlSession的getMapper方法,获得Mapper接口的动态代理对象MapperProxy,调用Mapper接口的所有方法都会调用到MapperProxy的invoke方法(动态代理机制)。

    2)MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象,然后调用这个对象的execute方法,sqlSession会作为execute方法的入参。

    3)往下,层层调下来会进入Executor组件(如果配置插件会对Executor进行动态代理)的query方法,这个方法中会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数。

    4)Executor组件有两个直接实现类,分别是BaseExecutor和CachingExecutor。CachingExecutor静态代理了BaseExecutor。Executor组件封装了Transction组件,Transction组件中又分装了Datasource组件。

    5)调用StatementHandler的增删改查方法获得结果,ResultSetHandler对结果进行封装转换,请求结束。

    6)Executor、StatementHandler 、ParameterHandler、ResultSetHandler,Mybatis的插件会对上面的四个组件进行动态代理。

    6.重要类

    MapperRegistry:本质上是一个Map,其中的key是Mapper接口的全限定名,value是MapperProxyFactory

    MapperProxyFactory:这个类是MapperRegistry中存的value值,在通过sqlSession获取Mapper时,其实先获取到的是这个工厂,然后通过这个工厂创建Mapper的动态代理类

    MapperProxy:实现了InvocationHandler接口,Mapper的动态代理接口方法的调用都会到达这个类的invoke方法

    MapperMethod:判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作

    SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能

    Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护

    StatementHandler: 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合

    ParameterHandler: 负责对用户传递的参数转换成JDBC Statement 所需要的参数

    ResultSetHandler: 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合

    TypeHandler: 负责java数据类型和jdbc数据类型之间的映射和转换

    MappedStatement: MappedStatement维护了一条节点的封装

    SqlSource: 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

    BoundSql: 表示动态生成的SQL语句以及相应的参数信息

    Configuration: MyBatis所有的配置信息都维持在Configuration对象之中

    7.调试主要关注点

    1)MapperProxy.invoke方法:MyBatis的所有Mapper对象都是通过动态代理生成的,任何方法的调用都会调到invoke方法,这个方法的主要功能就是创建MapperMethod对象,并放进缓存。所以调试时我们可以在这个位置打个断点,看下是否成功拿到了MapperMethod对象,并执行了execute方法。

    2)MapperMethod.execute方法:这个方法会判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作。Debug时也建议在此打个断点看下。

    3)DefaultSqlSession.selectList方法:这个方法获取了获取了MappedStatement对象,并最终调用了Executor的query方法。

    相关文章

      网友评论

        本文标题:MyBatis源码最后一天

        本文链接:https://www.haomeiwen.com/subject/gjaicdtx.html