type
status
date
slug
summary
tags
category
icon
password
MyBatis作为常用的ORM框架之一,以配置灵活、扩展方便为主要特点,从我们的业务环境使用来说,想要基于MyBatis与数据库进行交互,套路基本上都是固定的:
- 配置Mybatis环境:包括编写Mapper接口文件以及对应的SQL语句配置(XML配置或者注解配置),配置Mapper文件的扫描包(Spring环境下使用)
- 发起查询:当我们查询时都是首先获取到指定的Mapper实例,然后基于Mapper实例执行具体的方法调用获取查询结果。
相信刚开始使用MyBatis的同学都会有疑问——为什么我们基于Mapper接口发起查询并未编写任何的实现类,MyBatis就能够利用我们在接口方法注解上或者XML中指定的SQL向数据库发起查询呢?从这篇文章开始,我们来一步步揭开MyBatis查询过程的面纱,看看框架内部是如何将接口中定义的查询方法与具体的查询SQL语句关联起来执行的。
下面首先给出一段测试用例,摘选自MyBatis源码,可以看到我们只需要通过SqlSession获取到Mapper对象即可发起查询,使用起来非常简单。
既然通过SqlSession获取到Mapper实例发起查询就能够获取到结果,说明这与我们在项目中基于Mapper实例的查询过程原理是类似的,无非是在项目使用过程中我们是通过Spring的IOC工厂来获取不同的Mapper实例而已,下面就让我们通过SqlSession获取Mapper实例作为入口,开启MyBatis查询的源码分析之旅。
SqlSession介绍
The primary Java interface for working with MyBatis. Through this interface you can execute commands, get mappers and manage transactions.
通过MyBatis框架中对SqlSession属于MyBatis中的核心接口,通过SqlSession实例我们可以执行SQL命令,获取mapper以及管理事务。在MyBatis中我们与数据库的交互都是在SqlSession中完成的,下面我们首先看下应该如何获取SqlSession以及SqlSession中包含哪些内容。
在MyBatis中需要通过SqlSessionFactory获取SqlSession。
构建SqlSessionFactory
在MyBatis中需要通过SqlSessionFactory来获取SqlSession的实例,而我们每次发起数据库查询都需要通过SqlSession完成,因此该组件的生命周期应该为应用运行期间,在我们实际使用时可以将该组件设计为单例组件。
既然知道了如何获取SqlSession,那么我们如何获取SqlSessionFactory实例呢?通常情况下我们会创建SqlSessionFactoryBuilder来获取SqlSessionFactory,由于该组件在SqlSessionFactory创建完成后就没有任何意义了,因此SqlSessionFactoryBuilder的生命周期应该保持在方法级别。
SqlSessionFactoryBuilder中创建SqlSessionFactory的关键源码如下,我认为主要包含两部分内容:
- 配置解析:解析配置文件实例化为Configuration,这一步组件内提供了多种读取配置的方式使我们能够灵活选择,通过解析配置就能够将我们针对框架运行的所有配置以及我们所有的Mapper XML(或者注解)解析并存储到核心配置组件Configuration中(至于如何进行解析非常复杂,会将MyBatis主配置文件中指定的所有元数据解析之后保存到全局配置中心Configuration中,可以单独使用一篇文章进行说明);
- 基于核心配置类创建DefaultSqlSessionFactory作为默认工厂。
构建SqlSession
当我们获取到SqlSessionFactory实例后可以保存在应用全局中,之后在需要时就可以通过SqlSessionFactory实例来获取SqlSession执行查询。
当我们需要利用SqlSession完成数据库操作时,需要哪些信息呢?
- 数据库连接信息:需要知道当前查询语句需要在哪个数据库执行;
- 独立事务:不同查询应该在单独的事务中处理、事务的隔离级别;
- 提交机制:是否开启自动提交
下面我们通过DefaultSqlSessionFactory源码看下该组件是如何实例化SqlSession的。
在实例化SqlSession过程中最重要的一步就是需要明白DefaultSqlSessionFactory内部是如何创建Executor组件的,因为SqlSession本质上只是对Executor的封装,在真正查询时是需要通过Executor完成。
下面我们看下Configation类是如何创建Executor实例的。
通过上述源码我认为可以总结出如下内容:
- 多类型执行器支持:MyBatis中支持多种类型的Executor执行器,默认情况下会使用SimpleExecutor实例;
- 缓存机制的实现:通过delegate pattern将真正的执行器实现封装在CachingExecutor内部,在发起查询时进行拦截(后续会通过专门的文章来讲解MyBatis的缓存机制);
- 插件机制的体现:在创建执行器时我们首次了解到了MyBatis的插件机制,这是MyBatis给我们提供的扩展机制(后续会通过专门的文章讲解MyBatis的插件机制)。
通过我们上述结合源码的分析,可以了解到创建SqlSession在框架准备阶段就会触发所有Mapper注册到全局配置中心Configuration中,每当开启新的SqlSession时,MyBatis会通过全局配置中心来获取数据源信息,为当前会话创建新的事务,同时还会基于配置来创建Executor组件最终将这些信息封装到SqlSession内部以便之后使用。

获取Mapper实例
有了SqlSession实例后,我们就可以通过该组件获取Mapper实例来发起查询,现在我们需要通过源码来分析下获取到的Mapper实例的具体信息,这样才能后续继续跟踪基于Mapper查询的逻辑。
首先可以看到DefaultSqlSession会向configuration对象请求获取对应mapper对象的实例,这是因为MyBatis在解析阶段会将所有的Mapper与相关元数据都保存到configuration全局配置中,而MyBatis选择将Mapper与对应的代理工厂MapperProxyFactory维护在了MapperRegistry组件中。
注册中心MapperRegistry设计
MapperRegistry的重要源码如下,需要说明两点:
- 在框架初始化阶段会将所有指定包下配置的Mapper接口进行扫描,注册到MapperRegistry组件中,注册期间就会为每个Mapper接口创建一个代理工厂MapperProxyFactory;
- 在通过SqlSession获取Mapper实例时,会从MapperRegistry中查找对应Mapper类型的代理工厂,并使用JDK动态代理来创建Mapper实例对象。
Mapper代理工厂设计
下面我们梳理下Mapper代理工厂中的核心源码,我认为主要包括两部分重要内容:
- 享元思想的实践:由于每个Mapper对应唯一的代理工厂,因此在工厂内维护一份Mapper接口中所有方法与对应的方法调用器MapperMethodInvoker,使得方法调用组件不会被重复创建,提高性能;
- Mapper文件的解析:解析Mapper文件时会将我们定义下XML文件中的所有元数据解析出来,包括缓存配置、ResultMap配置以及不同类型的SQL语句等;
- 动态代理的实践:mapperProxyFactory通过JDK动态代理获取对应mapper的代理对象,用于后续SQL执行,也就是说我们通过SqlSession获取到的Mapper实例实际上是MyBatis内部经过层层处理之后创建的代理对象,更为具体的说是将MapperProxy实例作为InvocationHandler的JDK动态代理对象。
代理工厂存在的意义我认为包括两点:
- 便捷构造Mapper代理:为每个代理对象实例化新的MapperProxy
- 共享元数据:所有代理对象共享方法调用组件,这样就避免了对于Mapper中方法静态元数据的重复解析。
既然我们已经看到了通过SqlSession获取到的Mapper实例实际上是MyBatis框架为我们创建的动态代理对象,熟悉JDK动态代理的伙伴一定清楚,通过JDK动态代理创建代理对象时一定需要我们指定一个InvocationHandler实例,这样当我们向目标对象发起查询时,实际上InvocationHandler组件就会拦截我们对目标方法的调用,执行具体的业务逻辑,因此我们有必要了解清楚MyBatis是如何封装Mapper代理对象的InvocationHandler实例的。
通过查看MapperProxy的设计,可以发现其内部主要包括如下几部分关键信息:
- 当前代理对象所属的SqlSession实例,这表示着当前代理组件是为哪个会话服务的;
- 当前代理对象所属的Mapper接口;
- 当前代理的Mapper接口中所有方法与其对应的方法调用拦截器MapperMethodInvoker
我们知道调用JDK代理对象的方法时,一定会被代理对象对应InvocationHandler的invoke方法拦截,下面这段代码就是MapperProxy中具体实现:
既然MapperProxy作为Mapper代理,我们在通过获取到的Mapper实例发起查询时一定会被上述逻辑拦截执行,那这里就有两个问题:
- 查询方法这些静态的元数据在MyBatis框架启动后是不会变化的,MyBatis是如何管理这些Mapper关联的静态数据的?又是如何在多个代理对象之间共享的?
- MyBatis通过该组件是如何实现与我们自定义的SQL语句的关联的?
这些问题我们留下伏笔,相信通过下篇文章的源码分析,你会得到想要的答案。
总结
本文通过一个简单的测试用例展示了MyBatis通过Mapper实例如何发起查询,简单使用的背后是MyBatis为我们做了很多的工作,通过源码梳理我们不仅明白了SqlSession的创建过程以及MyBatis在默认实现中封装了哪些重要信息供我们查询使用,更重要的是我们终于了解到了日常使用中获取到的Mapper实例的真实身份——实际上是MyBatis框架内部帮助我们创建的JDK动态代理对象。
对于Mapper接口中指定方法的调用,实际上我们获取到的Mapper实例就是MyBatis框架创建的代理代理对象,因此对于Mapper接口中所有方法的调用会首先被代理对象接收到,而该代理对象对应的处理逻辑就是将不同的方法分发给对应的MapperMethodInvoker做进一步的处理。
下图是我基于MyBatis中获取Mapper代理的源码绘制的大致流程图,读者可以结合流程图以及源码多体会其中的设计亮点。
.png?table=block&id=f1adf169-566f-4a8c-8a07-4d531193ac39&t=f1adf169-566f-4a8c-8a07-4d531193ac39&width=1130&cache=v2)
有了Mapper代理对象,接下来我们就该揭开通过Mapper代理对象如何执行自定义查询语句的面纱了,下篇文章我们会继续跟踪源码分析MyBatis是如何发起数据库查询的,感兴趣的伙伴可以持续关注哦。

- 作者:Run
- 链接:https://blog.geekerit.com/article/MyBatis-Mapper
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。