在过去程序员使用 JDBC 连接数据库,总会带来诸多不便。MyBatis 是一款优秀的持久层框架,可以替代 JDBC 帮助我们更好的进行开发。要了解 MyBatis 的实现原理,首先我们要明白 MyBatis 的大致操作步骤。



数据库源告诉我们连接哪个数据库,获得要执行的SQL语句,再进行操作,这点者缺一不可。接下来要看的就是这三点在底层如何实现。

MyBatis 如何获取数据库源?

使用 Mybatis 第一步肯定是要写好配置文件。官方给出的指导文档告诉我们,XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源。

<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 我们要获取的数据库源信息在这里 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>

想要连接数据库,必然要获得数据源信息。既然上述配置文件有数据库源信息,那我们只要进行解析就好了。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

由代码可见,我们要的是一个 SqlSessionFactory 实例,SqlSessionFactory 里面就有我们所需的数据库源信息。通过new SqlSessionFactoryBuilder().build(inputStream) 返回SqlSessionFactory 实例,进入 build 方法。



build 方法返回值就是是 SqlSessionFactory,注意到有 return build(parser.parse()),关注点在 parser.parse(),进入 parse 方法。



parse 方法返回 Configuration 。parsed 是一个布尔类型成员变量,默认值是 false,作判断的目的是为了防止多线程情况下该方法被二次调用。这个方法返回一个 Configuration 类型的实例,Configuration 是 BaseBuilder 类的一个成员变量,Configuration 其实保存了配置文件所有的信息,只是现在还是一张白纸,需要再操作一番。进入 parseConfiguration 方法。



上一张图的 parse.evalNode 方法将配置文件中 configuration 标签下的内容进行解析,封装到一个对象,这个对象作为参数传入 parseConfiguration 方法中。在 parseConfiguration 方法我们见到了很多熟悉的字样,诸如 properties、typeAliases 之类的配置信息,但我们的目的是要拿到数据库源信息,因此我们把目标放在包裹了数据库源信息的 environments 标签上,进入 environmentsElement 方法。



作为参数的 context 对象与之前一样,封装了 environments 标签中的内容,我们还需要进一步解析 dataSource 标签,关注 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")) 这段代码,进入dataSourceElement 方法。



到这里 context 对象只有 dataSource 里的内容了。发现 type 的值为 POOLED(默认值),props 保存最终的数据库配置信息。DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance() 这一段代码,进入 resolveClass 方法,最终再跳转 resolveAlias 方法中。



注意 value = (Class) typeAliases.get(key),typeAliases 实际上是一个 HashMap,将 POOLED 作为 key 得到了保存的对应的 Class 类型。回到 dataSourceElement 方法。



得到返回的 Class 返回值,利用反射 newInstance 创建对应的 DataSourceFactory 对象,set 方法保存 props ,回到 environmentsElement 方法。



继续执行后面的方法,最终数据库源信息封装到一个 Environment 类型的实例,这个实例又通过 set 方法保存到了 configuration 。configuration 已经处理就绪,被 parse 方法返回。回到之前的 build 方法,将 configuration 作为参数传入至另一个重载的 build 方法。



SqlSessionFactory 本身是一个接口,DefaultSqlSessionFactory 则是实现了 SqlSessionFactory 的实现类,保存好 configuration 之后返回,就得到了我们开头需要的 SqlSessionFactory 实例。

MyBatis 如何获取 sql 语句?

与获取数据库源类似,只要解析 Mapper 配置文件中的对应标签,就可以获得对应的 sql 语句。之前我们讲过,SqlSessionFactory 中的 configuration 属性保存数据库源信息,事实上这个 configuration 属性将整个配置文件的信息都给封装成一个类来保存了。解析的前半部分与之前一样,分歧点在之前提到的 parseConfiguration 方法,其中在 environmentsElement 方法下面还有一个 mapperElement 方法。



配置文件中 mappers 标签加载mapper文件的方式共有四种:resource、url、class、package。代码中的 if-else 语句块分别判断四种不同的加载方式,可见 package 的优先级最高。parent 是配置文件中 mappers 标签中的信息,通过外层的循环一个一个读取多个 Mapper 文件。这里使用的方式是 resource ,所以会执行光标所在行的代码块,进入 mapperParser.parse() 方法。



我们要的是 mapper 标签的内容,因此我们关注 configurationElement(parser.evalNode("/mapper")) 这一句,进入 configurationElement 方法。



context 就是我们解析整个 Mapper 文件 mapper 标签中的内容,既然现在得到了内容,那只需再找到对应的标签就能获得sql语句了。注意 buildStatementFromContext(context.evalNodes("select|insert|update|delete")),我们看到了熟悉的 select、insert、update、delete,这些标签里就有我们写 sql 语句。进入 buildStatementFromContext 方法。



list 保存了我们在 Mapper 文件中写的所有含有 sql 语句的标签元素,用一个循环遍历 list 的每一个元素,分别将每一个元素的信息保存到 statementParser 中。进入 parseStatementNode 方法。





这个方法代码内容很多,仅摘出节选,里面定义了很多局部变量,这些变量用来保存sql语句标签(例如)的参数信息(例如缓存 useCache)。再把所有参数传到 addMappedStatement 中。进入 addMappedStatement 方法。



MappedStatement statement = statementBuilder.build(),使用 build 方法得到 MappedStatement 实例,这个类封装了每一个含有sql语句标签中所有的信息,再是 configuration.addMappedStatement(statement),保存到 configuration 中。

MyBatis 如何执行 sql 语句?

既然有了 SqlSessionFactory,我们可以从中获得 SqlSession 的实例。开启 session 的语句是 SqlSession session = sessionFactory.openSession(),进入 openSession 方法。



最终会执行 openSessionFromDataSource 方法。在之前 environment 已经有了数据库源信息,调用 configuration.newExecutor 方法。



Executor 叫做执行器,Mybatis 一共有三种执行器,用一个枚举类 ExecutorType 保存,分别是 SIMPLE,REUSE,BATCH,默认就是 SIMPLE。if-else 语句判断对应的类型,创建不同的执行器。在代码末端处有个 if 判断语句,如果 cacheEnabled 为 true,则会创建缓存执行器,默认是为 true,即默认开启一级缓存。

回到 openSessionFromDataSource 方法,最终返回一个 DefaultSqlSession 实例。得到 session 我们就可以执行 sql 语句了。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句,以 selectOne 方法为例,进入该方法后发现,最终会调用到 selectList 方法。



configuration.getMappedStatement(statement) 得到了我们之前保存的 MappedStatement 对象,再调用 executor.query 方法,调用 query 方法之前会执行 wrapCollection 方法,保存 sql 语句中用户传入的参数。进入 query 方法。



boundSql 里面就有我们要执行的 sql 语句,CacheKey 是用来开启缓存的。执行父类 BaseExecutor 中的 createCacheKey 方法,通过 id,offsetid,limited,sql 组成一个唯一的 key,调用下一个 query 方法。



Cache cache = ms.getCache() 是二级缓存,二级缓存为空,直接调用 query 方法。



list = resultHandler == null ? (List) localCache.getObject(key) : null 传入 key 值在本地查询,如果有返回证明 key 已经缓存到本地,直接从本地缓存获取结果。否则 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql),去数据库查询。



localCache.putObject(key, EXECUTION_PLACEHOLDER) 首先将 key 缓存至本地,下一次查询就能找到这个 key 了。进入 doQuery 方法。



stmt = prepareStatement(handler, ms.getStatementLog()),得到一个 Statement。进入 prepareStatement 方法。



我们看到了一个熟悉的 Connection 对象,这个就是原生 JDBC 的实例对象。回到 doQuery 方法,进入 handler.query(stmt, resultHandler) 方法。



statement 强转型为 PreparedStatement 类型,这下我们又得到了 PreparedStatement 的类型实例了,调用 execute 方法,这个方法也是属于原生 JDBC。执行完成后 return resultSetHandler.handleCursorResultSets(ps),进入 handleCursorResultSets 方法。



ResultSetWrapper rsw = getFirstResultSet(stmt),看到 getFirstResultSet 方法中的 ResultSet rs = stmt.getResultSet(),在这里我们得到了 ResultSet 实例对象,最终 return rs != null ? new ResultSetWrapper(rs, configuration) : null,返回最终结果集。

MyBatis 如何实现不同类型数据之间的转换?

进入上一张图中 ResultSetWrapper 中可以看到,其中包含三个成员变量 columnNames、classNames、jdbcTypes,三者都是 ArrayList 集合。看一下构造方法。



final ResultSetMetaData metaData = rs.getMetaData(),metaData 就是数据库相关的数据,getColumnCount 统计有多少个字段,循环加入到 columnNames、jdbcTypes、classNames。columnNames 保存的就是实体类中的属性名,jdbcTypes 保存的是字段在数据库中的数据类型,classNames 保存的是字段在 Java 中的数据类型,比如 Java 的 String 与数据库 VARCHAR,MyBatis 充当一个中介完成转换,真正实现 ORM 的核心思想。

学习 MyBatis 的一点小总结 —— 底层源码初步分析的更多相关文章

  1. Java线程池及其底层源码实现分析

    1.相关类 Executors  ExecutorService   Callable   ThreadPool     Future 2.相关接口 Executor Executor接口的使用: p ...

  2. vue-style-loader源码初步分析

    背景: 首先声明一下,我只是个菜鸡,为了解决问题才去看的源码,解决完问题之后也就没有兴趣看其他部分代码了,所以这篇文章是一次很低层次的解读,角度也相当片面,想必会有很多喷点吧. 事情的经过是这样,今年 ...

  3. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

  4. 【小盘子看源码-MyBatis-1】MyBatis配置文件的加载流程

    众所周知,Mybatis有一个全局的配置,在程序启动时会加载XML配置文件,将配置信息映射到org.apache.ibatis.session.Configuration类中,例如如下配置文件. &l ...

  5. Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  6. 全方位深度剖析PHP7底层源码(已完结)

    第1章 课程介绍本章主要介绍课程要讲的知识点,以及课程要求等. 第2章 PHP7的新特性本章主要介绍PHP7的新特性,做基准测试,与PHP5对比验证PHP7的性能提升程度,引出对PHP7源码学习的必要 ...

  7. BAT资深工程师 由浅入深分析 Tp5&Tp6底层源码 - 分享

    BAT资深工程师由浅入深分析Tp5&Tp6底层源码 第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的 ...

  8. BAT资深工程师由浅入深分析Tp5&Tp6底层源码☆

    第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的帮助. 第2章 [TP5灵魂]自动加载Loader 深度分析 ...

  9. Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析

    目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...

随机推荐

  1. 并查集(不相交集)的Remove操作

    给并查集(不相交集)的添加一个\(Remove(X)\)操作,该操作把\(X\)从当前的集合中除去并把它放到自己的集合中. 实现思想 英文原句 We assume that the tree is i ...

  2. 7-3 jmu-python-回文数判断(5位数字) (10 分)

    本题目要求输入一个5位自然数n,如果n的各位数字反向排列所得的自然数与n相等,则输出‘yes’,否则输出‘no’. 输入格式: 13531 输出格式: yes 输入样例1: 13531 输出样例1: ...

  3. 添加谷歌拓展程序 vue.js devtools过程中的问题

    在用vue做项目过程中,需要用到vue.js devtools,在从github上面clone下来代码,然后再npm install ,过程报错,然后更新npm包也是会有问题,以下是install的问 ...

  4. SpringBoot内置的各种Starter是怎样构建的?--SpringBoot源码(六)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 温故而知新 本篇接 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五) 温 ...

  5. C语言程序设计(六) 循环控制结构

    第六章 循环控制结构 循环结构:需要重复执行的操作 被重复执行的语句序列称为循环体 计数控制的循环 条件控制的循环 当型循环结构 直到型循环结构 for while do-while while(循环 ...

  6. python3.4.3 连接Oracle生成报表并发送邮件

    python很简单,又很实用.当有需求时用起来会更有方向,大可不必从语法.循环等基础看起. 由于工作需要,每天要拉一份报表发给业务的同事,先是用SSIS做了个包部署到服务器上,每天定时拉报表发邮件给同 ...

  7. Git&sourceTree软件安装、使用说明及遇到问题解决

    一.软件版本 1.Git版本为1.9.5 2.Source版本为1.5.2 二.软件安装步骤 1.Git安装步骤 1)双击Git安装文件进入下图界面,单击Next 2)继续Next 3)进入Selec ...

  8. 使用ASP.NET MVC 5快速实现芒果分销后台管理系统(前言)

    ### 前言 在前一篇文章中,我提到最近要陆续为大家写一些.Net实战技术文章.从今天起,我将围绕一个入门级现实的芒果分销管理系统案例,使用ASP.NET MVC 5,从前端到后端,一步一步为大家呈现 ...

  9. Android网络开发

    1. WebView用法 ①布局文件新建一个WebView,特别注意线性布局和控件的宽高都要匹配父控件 <LinearLayout xmlns:android="http://sche ...

  10. SQL Prompt9 注册教程

    在网上找了很多,都是9.0一下的版本,所以这边来发个9.0的版本 然后下载的安装文件应该包括 这两个: 第一个是注册机,第二个是安装包,安装这里就不用讲了,下一步下一步就完事,安装完成之后,一定要断网 ...