目录
- MyBatis 如何获取数据库源?
- MyBatis 如何获取 sql 语句?
- MyBatis 如何执行 sql 语句?
- MyBatis 如何实现不同类型数据之间的转换?
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/201422C51-0.jpg)
文章图片
数据库源告诉我们连接哪个数据库,获得要执行的SQL语句,再进行操作,这点者缺一不可。接下来要看的就是这三点在底层如何实现。
MyBatis 如何获取数据库源? 使用 Mybatis 第一步肯定是要写好配置文件。官方给出的指导文档告诉我们,XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源。
想要连接数据库,必然要获得数据源信息。既然上述配置文件有数据库源信息,那我们只要进行解析就好了。
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 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014225225-1.jpg)
文章图片
build 方法返回值就是是 SqlSessionFactory,注意到有 return build(parser.parse()),关注点在 parser.parse(),进入 parse 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014221102-2.jpg)
文章图片
parse 方法返回 Configuration 。parsed 是一个布尔类型成员变量,默认值是 false,作判断的目的是为了防止多线程情况下该方法被二次调用。 这个方法返回一个 Configuration 类型的实例,Configuration 是 BaseBuilder 类的一个成员变量,Configuration 其实保存了配置文件所有的信息,只是现在还是一张白纸,需要再操作一番 。进入 parseConfiguration 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/201422D12-3.jpg)
文章图片
上一张图的 parse.evalNode 方法将配置文件中 configuration 标签下的内容进行解析,封装到一个对象,这个对象作为参数传入 parseConfiguration 方法中。在 parseConfiguration 方法我们见到了很多熟悉的字样,诸如 properties、typeAliases 之类的配置信息,但我们的目的是要拿到数据库源信息,因此我们把目标放在包裹了数据库源信息的 environments 标签上,进入 environmentsElement 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014226160-4.jpg)
文章图片
作为参数的 context 对象与之前一样,封装了 environments 标签中的内容,我们还需要进一步解析 dataSource 标签,关注 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")) 这段代码,进入dataSourceElement 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014222101-5.jpg)
文章图片
到这里 context 对象只有 dataSource 里的内容了。发现 type 的值为 POOLED(默认值),props 保存最终的数据库配置信息。DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance() 这一段代码,进入 resolveClass 方法,最终再跳转 resolveAlias 方法中。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014221518-6.jpg)
文章图片
注意 value = https://www.it610.com/article/(Class
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014226413-7.jpg)
文章图片
得到返回的 Class 返回值,利用反射 newInstance 创建对应的 DataSourceFactory 对象,set 方法保存 props ,回到 environmentsElement 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014223P4-8.jpg)
文章图片
继续执行后面的方法,最终数据库源信息封装到一个 Environment 类型的实例,这个实例又通过 set 方法保存到了 configuration 。configuration 已经处理就绪,被 parse 方法返回。回到之前的 build 方法,将 configuration 作为参数传入至另一个重载的 build 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014225163-9.jpg)
文章图片
SqlSessionFactory 本身是一个接口,DefaultSqlSessionFactory 则是实现了 SqlSessionFactory 的实现类,保存好 configuration 之后返回,就得到了我们开头需要的 SqlSessionFactory 实例。
MyBatis 如何获取 sql 语句? 与获取数据库源类似,只要解析 Mapper 配置文件中的对应标签,就可以获得对应的 sql 语句。之前我们讲过,SqlSessionFactory 中的 configuration 属性保存数据库源信息,事实上这个 configuration 属性将整个配置文件的信息都给封装成一个类来保存了。解析的前半部分与之前一样,分歧点在之前提到的 parseConfiguration 方法,其中在 environmentsElement 方法下面还有一个 mapperElement 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014226138-10.jpg)
文章图片
配置文件中 mappers 标签加载mapper文件的方式共有四种:resource、url、class、package。代码中的 if-else 语句块分别判断四种不同的加载方式,可见 package 的优先级最高。parent 是配置文件中 mappers 标签中的信息,通过外层的循环一个一个读取多个 Mapper 文件。这里使用的方式是 resource ,所以会执行光标所在行的代码块,进入 mapperParser.parse() 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014222F9-11.jpg)
文章图片
我们要的是 mapper 标签的内容,因此我们关注 configurationElement(parser.evalNode("/mapper")) 这一句,进入 configurationElement 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/20142263K-12.jpg)
文章图片
context 就是我们解析整个 Mapper 文件 mapper 标签中的内容,既然现在得到了内容,那只需再找到对应的标签就能获得sql语句了。注意 buildStatementFromContext(context.evalNodes("select|insert|update|delete")),我们看到了熟悉的 select、insert、update、delete,这些标签里就有我们写 sql 语句。进入 buildStatementFromContext 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014221M5-13.jpg)
文章图片
list 保存了我们在 Mapper 文件中写的所有含有 sql 语句的标签元素,用一个循环遍历 list 的每一个元素,分别将每一个元素的信息保存到 statementParser 中。进入 parseStatementNode 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014222191-14.jpg)
文章图片
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014222A3-15.jpg)
文章图片
这个方法代码内容很多,仅摘出节选,里面定义了很多局部变量,这些变量用来保存sql语句标签(例如 )的参数信息(例如缓存 useCache)。再把所有参数传到 addMappedStatement 中。进入 addMappedStatement 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/201422Bc-16.jpg)
文章图片
MappedStatement statement = statementBuilder.build(),使用 build 方法得到 MappedStatement 实例,这个类封装了每一个含有sql语句标签中所有的信息,再是 configuration.addMappedStatement(statement),保存到 configuration 中。
MyBatis 如何执行 sql 语句? 既然有了 SqlSessionFactory,我们可以从中获得 SqlSession 的实例。开启 session 的语句是 SqlSession session = sessionFactory.openSession(),进入 openSession 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014224Z4-17.jpg)
文章图片
最终会执行 openSessionFromDataSource 方法。在之前 environment 已经有了数据库源信息,调用 configuration.newExecutor 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014222927-18.jpg)
文章图片
Executor 叫做执行器,Mybatis 一共有三种执行器,用一个枚举类 ExecutorType 保存,分别是 SIMPLE,REUSE,BATCH,默认就是 SIMPLE。if-else 语句判断对应的类型,创建不同的执行器。在代码末端处有个 if 判断语句,如果 cacheEnabled 为 true,则会创建缓存执行器,默认是为 true,即默认开启一级缓存。
回到 openSessionFromDataSource 方法,最终返回一个 DefaultSqlSession 实例。得到 session 我们就可以执行 sql 语句了。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句,以 selectOne 方法为例,进入该方法后发现,最终会调用到 selectList 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014226331-19.jpg)
文章图片
configuration.getMappedStatement(statement) 得到了我们之前保存的 MappedStatement 对象,再调用 executor.query 方法,调用 query 方法之前会执行 wrapCollection 方法,保存 sql 语句中用户传入的参数。进入 query 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014221964-20.jpg)
文章图片
boundSql 里面就有我们要执行的 sql 语句,CacheKey 是用来开启缓存的。执行父类 BaseExecutor 中的 createCacheKey 方法,通过 id,offsetid,limited,sql 组成一个唯一的 key,调用下一个 query 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/201422DD-21.jpg)
文章图片
Cache cache = ms.getCache() 是二级缓存,二级缓存为空,直接调用 query 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/20142249A-22.jpg)
文章图片
list = resultHandler == null ? (List
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014222W7-23.jpg)
文章图片
localCache.putObject(key, EXECUTION_PLACEHOLDER) 首先将 key 缓存至本地,下一次查询就能找到这个 key 了。进入 doQuery 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014224X3-24.jpg)
文章图片
【数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析】stmt = prepareStatement(handler, ms.getStatementLog()),得到一个 Statement。进入 prepareStatement 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/20142215M-25.jpg)
文章图片
我们看到了一个熟悉的 Connection 对象,这个就是原生 JDBC 的实例对象。回到 doQuery 方法,进入 handler.query(stmt, resultHandler) 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014225A0-26.jpg)
文章图片
statement 强转型为 PreparedStatement 类型,这下我们又得到了 PreparedStatement 的类型实例了,调用 execute 方法,这个方法也是属于原生 JDBC。执行完成后 return resultSetHandler.handleCursorResultSets(ps),进入 handleCursorResultSets 方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014222b5-27.jpg)
文章图片
ResultSetWrapper rsw = getFirstResultSet(stmt),看到 getFirstResultSet 方法中的 ResultSet rs = stmt.getResultSet(),在这里我们得到了 ResultSet 实例对象,最终 return rs != null ? new ResultSetWrapper(rs, configuration) : null,返回最终结果集。
MyBatis 如何实现不同类型数据之间的转换? 进入上一张图中 ResultSetWrapper 中可以看到,其中包含三个成员变量 columnNames、classNames、jdbcTypes,三者都是 ArrayList 集合。看一下构造方法。
![数据库|学习 MyBatis 的一点小总结 —— 底层源码初步分析](http://img.readke.com/220618/2014224L8-28.jpg)
文章图片
final ResultSetMetaData metaData = https://www.it610.com/article/rs.getMetaData(),metaData 就是数据库相关的数据,getColumnCount 统计有多少个字段,循环加入到 columnNames、jdbcTypes、classNames。columnNames 保存的就是实体类中的属性名,jdbcTypes 保存的是字段在数据库中的数据类型,classNames 保存的是字段在 Java 中的数据类型,比如 Java 的 String 与数据库 VARCHAR,MyBatis 充当一个中介完成转换,真正实现 ORM 的核心思想。
推荐阅读
- MyBatis|MyBatis官方教程及源码解析——入门
- 数据库|redis(redis的底层数据结构)
- JAVA基础|JDK动态代理
- 中间件|Redis
- springboot|SpringBoot集成JWT实现token验证
- 全文检索|springboot整合
- 全文检索|elasticsearch&Kibana安装
- 全文检索|初步检索及进阶检索
- mysql 比text还大的类型