MyBatis中一个SQL语句的执行过程解析
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
平时用MyBatis框架开发时,配置好config.xml和mapper.xml映射文件和定义好java接口,就可以操作数据库了,
当然也可以像spring一样基于注解配置,看个人情况。
mybatis配置文件
mybatis mapper映射文件(由于模块众多,mapper映射文件很多,只列举一个)
然后只需定义好java接口,就能操作数据库了
测试代码:
执行结果:
实在简单,我就简单介绍下mybatis怎么一步一步运行起来的,下面才是重点
1. xml文件解析阶段,怎么把xml文件解析成Configuration对象(很重要的一个全局性对象)
1.1 MyBatis Config xml配置文件
它是一种符合DTD约束的一种配置文件,可以详细看它如何规范的http://mybatis.org/dtd/mybatis-3-config.dtd,
具体表现可在config.xml配置文件中查看其组成部分 https://mybatis.org/mybatis-3/zh/configuration.html,现在流行注解,不知道能不能很好的表现这种结构,dom树结构
Mybatis要把该xml文件解析成一个全局性的Configurationl类型的java对象,它的类定义诸多属性,该文件在org.apache.ibatis.session包下
下面开始对配置文件进行处理,希望你有java和xml数据绑定的开发经历,可以参考Java and XML Data Binding.pdf https://github.com/dongguangming/java/blob/master/O'Reilly%20-%20Java%20and%20XML%20Data%20Binding.pdf
首先,我们使用 MyBatis 自带的工具类 Resources 读取加载配置文件,得到一个输入流。
然后再通过 SqlSessionFactoryBuilder 对象的build
方法构建 SqlSessionFactory 对象。
从图片上看到MyBatis 配置文件是通过XMLConfigBuilder
进行parse()解析的,继续一层一层往下看
接着调用parseConfiguration(),刚好11个直接子节点,一个个分别解析处理
这样,一个 MyBatis 的解析过程就出来了,每个配置的解析逻辑都封装在了相应的方法中。由于解析模块众多,我就选几个了,要不然写不完
1.1.1 解析 properties 配置
mysql.properties属性文件,内容如下
解析properties
节点是由propertiesElement
这个方法完成的,在上面的配置中, properties 节点配置了一个 resource 属性。下面我们参照上面的配置,来分析一下 propertiesElement 的逻辑。
properties 节点解析的主要过程主要包含三个步骤,一是解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。二是从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。最后一步则是将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。
需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。
1.1.2 解析 settings 配置
1.1.2.1 settings 节点的解析过程
settings 配置是 MyBatis 中非常重要的配置,这些配置用于调整 MyBatis 运行时的行为。settings 配置繁多,在对这些配置不熟悉的情况下,保持默认配置即可。关于 settings 相关配置,MyBatis 官网上进行了比较详细的描述,https://mybatis.org/mybatis-3/zh/configuration.html#settings,我就以我的配置举例
接下来,对照上面的配置,来分析源码。如下:
注意由于节点多,导致xml dom解析逻辑判断也多,就不一一举例细节了,但逻辑不是技术,只需要知道会生成一个Configurationl类型的java对象即可。
2. SQL语句的执行流程
再次贴下代码
MybatisDao ud= sqlSession.getMapper(MybatisDao.class);会返回一个代理对象,继续追踪sqlSession.getMapper(MybatisDao.class)是如何实现的,
而knowMappers实际上存放的是
MapperProxyFactory定义如下
MapperProxy定义如下,实现了InvocationHandler接口
然后通过调用mapperProxyFactory.newInstance(sqlSession)返回代理对象
这下明白很多文章说为啥说mybatis只定义接口就能调用方法了。
此时控制台输出
紧接着就是调用接口方法(注意是代理对象调用方法):User user = ud.selectUserById(1);
怎么拿方法不一一细看,又是一大堆逻辑,这里只看最后一句执行命令:mapperMethod.execute(sqlSession, args);
具体代码如下
此例子中其实会执行select
session是DefaultSqlSession类型的,因为sqlSessionFactory默认生成的SqlSession是DefaultSqlSession类型。selectOne()会调用selectList()。
如图
在DefaultSqlSession.selectList中的各种CURD操作都是通多Executor进行的,这里executor的类型是CachingExecutor,接着跳转到其中的query方法中。
getBoundSql为了获取绑定的sql命令,在创建完cacheKey之后,就进入到CachingExecutor 类中的另一个query方法中。
这里真正执行query操作的是SimplyExecutor代理来完成的,接着就进入到了SimplyExecutor的父类BaseExecutor的query方法中。
此时可以断定是第一次SQL查询操作,
所以会调用queryFromDatabase方法来执行查询。
从数据库中查询数据,调用doQuery方法,进入到SimplyExecutor中进行操作。
特别注意,在prepareStatement方法中会进行SQL查询参数的设置,也就是咱们最开始传递进来的参数,其值为1。handler.<E>query(stmt)方法中会进行实际的SQL查询操作和结果集的封装(封装成Java对象)。
prepareStatement方法阶段(即设置SQL查询参数):
通过getConnection方法来获取一个Connection,调用prepare方法来获取一个Statement(这里的handler类型是RoutingStatementHandler,RoutingStatementHandler的prepare方法调用的是PrepareStatementHandler的prepare方法,因为PrepareStatementHandler并没有覆盖其父类的prepare方法,其实最后调用的是BaseStatementHandler中的prepare方法。是)。调用parameterize方法来设置SQL的参数值(这里最后调用的是PrepareStatementHandler中的parameterize方法,而PrepareStatementHandler.parameterize方法调用的是DefaultParameterHandler中的setParameters方法)。
此时已经给Statement设置了最初传递进去的参数。
那么接着分析流程2:
handler.<E>query(stmt)方法阶段(SQL查询及结果集的设置):
ResultSetWrapper是ResultSet的包装类,调用getFirstResultSet方法获取第一个ResultSet,同时获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等,这些信息都存储在ResultSetWrapper类中了。然后调用handleResultSet方法来来进行结果集的封装。
这里调用handleRowValues方法来进行值的设置:
mapping.typeHandler.getResult会获取查询结果值的实际类型,比如我们user表中id字段为int类型,那么它就对应Java中的Integer类型,然后通过调用statement.getInt("id")来获取其int值,其类型为Integer。metaObject.setValue方法会把获取到的Integer值设置到Java类中的对应字段。
metaValue.setValue方法最后会调用到Java类中对应数据域的set方法,这样也就完成了SQL查询结果集的Java类封装过程。
至此,分析完毕。和spring有点类似,只是方向不一样,都是大量的dom解析(现在是java注解比较多了)成全局java文件,然后结合逻辑编码实现相应的功能。
但记住:逻辑往往不是技术,如何构思、组装才是重点!!!
注意: 你如果只是想使用mybatis(写下配置文件和mapper文件)就不需要看此文了,也没什么必要!
课题:留给你们一个分页插件,构思设想和编码如何实现???
后记:
请你们务必灵活运用这些器:分发器,过滤器,拦截器,监听器,反应堆器,特别注意这跟语言、框架无关。
当然还有操作系统相关知识,希望其他人早意识到cpu、内存分配、io模型、进程/线程等是很重要的(会让你更好的理解一些库比如libevent和中间件的实现原理),也跟语言、库、框架无关,而不管你用java、scala还是golang实现。
因为很少有网课关于操作系统和网络的系统化培训,那还是让我董广明告诉要学什么,如下
可能市面上做crud的开发者居多,那数据库要留意下。
已把mybatis电子书上传,很简单 https://github.com/dongguangming/java/blob/master/MyBatis/Java%20Persistence%20with%20MyBatis%203(%E4%B8%AD%E6%96%87%E7%89%88).pdf
参考:
0. MyBatis事务 https://blog.csdn.net/dong19891210/article/details/105672535
-
SpringBoot : Working with MyBatis https://www.sivalabs.in/2016/03/springboot-working-with-mybatis/
-
mybatis配置 https://mybatis.org/mybatis-3/zh/configuration.html
-
Mybatis source code analysis https://developpaper.com/mybatis-source-code-analysis/
-
Mybatis Source Code Analysis https://programming.vip/docs/mybatis-source-code-analysis.html
-
MyBatis source code analysis https://programmer.group/mybatis-source-code-analysis.html