前记:上期讲了Mybatis的源码环境搭建,现在顺着这个往下学习。通过Debug方式去解析源码的执行流程来了解Mybatis的内部原理。
环境准备:
运行一下结果如下:
准备已经完成下面以Debug方式进入源码学习。
首先介绍一下Mybatis所起到的作用(这里可以去参考mybatis官网http://www.mybatis.org/mybatis-3/zh/index.html)
官网的介绍:
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
==========================开始进入源码=======================================
1、看测试案例
通过1和2获取到基本配置,然后通过SqlSessionFactoryBuilder这个类build方法去构建SqlSessionFactory工厂。
Debug进入build方法(从连接或数据源创建SqlSession)
这行代码比较重要,进入看看他做了什么
这里有个/configuration 有点熟悉不知道什么意思,但是中文翻译过来就是配置的意思,在看这个方法parseConfiguration翻译过来就是"解析配置",这里应该懂了,他应该是去解析之前准备的mybatis-config.xml的配置。 继续往下走看看
看到这里应该明白了他就是去解析配置的
看一下配置,明白过来了,这里也明白了为什么他的源码是 parseConfiguration(parser.evalNode("/configuration"));他为什么是"/configuration" 他是要去解析这个标签下的配置信息这里面的标签的含义,在mybatis官网有介绍的http://www.mybatis.org/mybatis-3/zh/configuration.html
这里我对这块源码加了注释(具体可以到官网学习)
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
/**解析配置文件中的各种属性*/
propertiesElement(root.evalNode("properties"));
/**解析mybatis的全局设置信息*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
/**解析别名配置*/
typeAliasesElement(root.evalNode("typeAliases"));
/**解析插件配置*/
pluginElement(root.evalNode("plugins"));
/**解析对象工厂元素*/
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
/**解析mybatis的环境配置*/
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/**解析类型处理器配置信息*/
typeHandlerElement(root.evalNode("typeHandlers"));
/**解析mapper配置信息*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
现在拆解一下几个关键方法
插件加载:
通常有数据库特殊字段转化时(mysql:datatime->java:String)可以重写这拦截器方法
环境配置加载:
拿到我们的配置信息了
下面去看如何解析mapper文件的
看一下ProductMapper.xml的信息
返回在看如何解析mapper 我们进入到这个方法中->org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
进入org.apache.ibatis.builder.xml.XMLMapperBuilder#parse方法去详细了解如何解析sql语句,返回数据的
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement去如何操作
然后在看看如何解析sql语句的
获取到sql拼接的片段
(注意:这里解析是所有的sql并不是所有的查询语句,我注释写错了)
下面进入org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace 看看如何绑定映射器的、
在解析完配置后将信息(账号密码驱动等配置信息)保存在Configuration中去。返回后看一下
读取到配置信息了,还不止这些
总而言之,org.apache.ibatis.builder.xml.XMLConfigBuilder.parse解析了配置信息。
由上可知mybatis是如何获取数据源:
>org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)
>org.apache.ibatis.builder.xml.XMLConfigBuilder
org.apache.ibatis.builder.xml.XMLConfigBuilder.parse
>org.apache.ibatis.builder.xml.XMLConfigBuilder.environmentsElement
>org.apache.ibatis.datasource.DataSourceFactory.getDataSource
>org.apache.ibatis.session.Configuration.setEnvironment###### set数据源
也可以知道是如何构建SQL语句:
>org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)
>org.apache.ibatis.builder.xml.XMLConfigBuilder
>org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement
>org.apache.ibatis.builder.xml.XMLMapperBuilder
>org.apache.ibatis.builder.xml.XMLMapperBuilder.parse
>org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)
>org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode
>org.apache.ibatis.session.Configuration.addMappedStatement ######set 这个sql
=============================创建会话================================
.
下面就是创建一个会话了 SqlSession session = sessionFactory.openSession();
再看看Mybatis是如何创建会话的 f7进入
进入org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource方法
获取Executor对象,进入org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)方法
从上面源码可知:1、默认SIMPLE类型的执行器,2、ExecutorType有三种(可以点开查看)3、cacheEnabled二级缓存是默认开启的。继续往下走他会去执行插件(拦截器,这里我们没配置,略过)
返回一个sqlSession对象
继续往下走:
如上图开始实例化ProductMapper(代理),如何生成?
knownMappers属性里面的值,实际上就是我们在mappers扫描与解析的时候放进去的。 下面进入org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)如下:
看这个MapperProxy,点进去发现他是继承jdk的InvocationHandler接口,从以上代码可以看出,实际上使用的就是jdk的动态代理,给UserMapper接口生成一个代理对象。实际上就是MapperProxy的一个对象。如图:
所以整个代理对象生成过程可以用如下时序图表示:
=========================执行sql语句=======================
F7进入org.apache.ibatis.binding.MapperProxy#invoke
UserMapper实际上是代理对象MapperProxy,所以我们执行查询语句的时候实际上执行的是MapperProxy的invoke方法:
可以看到,先根据方法签名,从方法缓存中获取方法,如果为空,则生成一个MapperMethod放入缓存并返回。如下两段代码:
我们深入了解细节type,returnType status
type:进入org.apache.ibatis.binding.MapperMethod.SqlCommand#resolveMappedStatement 拿到方法与sql类型
returntype:org.apache.ibatis.binding.MapperMethod.MethodSignature#MethodSignature
status:org.apache.ibatis.reflection.ParamNameResolver#ParamNameResolver
继续执行(最终执行查询的是MapperMethod的execute方法):进入org.apache.ibatis.binding.MapperMethod#execute
根据上面拿到sql类型是查询SELECT进入查询方法,根据我们返回数据的类型(是多个many)
我们查询示例是list,然后进入org.apache.ibatis.binding.MapperMethod#executeForMany
进入org.apache.ibatis.reflection.ParamNameResolver#getNamedParams拿到参数(status的值)
参数处理完接下来就是调用执行过程,最终调用执行的是DefaultSqlSession中的selectList方法:org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
获取到查询语句等一系列参数。
进入:org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
进入:org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
他会去调用这个查询方法org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
先从缓存中去查,如果没有那就从数据库查询(org.apache.ibatis.executor.BaseExecutor#queryFromDatabase)
从数据库中查询,现在有个重要方法:org.apache.ibatis.executor.SimpleExecutor#doQuery如下:
进入:org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler
然后进入:org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
返回来:进入org.apache.ibatis.executor.SimpleExecutor#prepareStatement
这是我们看下这个connection,为啥要看这个,因为我们看到一个方法prepareStatement(...)貌似是jdbc的方法,我们进入getConnetion方法(其实这个Connetion类就是在java.sql.Connection):
这回connction就是创建jdbc的链接。说明啥-->mybatis就是对jdbc的一种封装,变得更加便捷。
回到doQuery方法,进入:org.apache.ibatis.executor.statement.PreparedStatementHandler#query
然后进入:org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getFirstResultSet
这里有个resultset
进入:org.apache.ibatis.executor.resultset.ResultSetWrapper#ResultSetWrapper
这里我们着重看一下这三个list:
发现这是将jdbc字段类型与转化为java的数据类型.
这里提出一句:ROM框架(对象关系映射(Object Relational Mapping))技术本质就是实现不同语言之间的不同类型数据之间的转换
继续往下走:
此时已经拿到结果集了,开始返回
此时我们的list就拿到结果了
说明从数据中已经查询到数据
那么查询数据的流程如下:
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession()
>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource
>org.apache.ibatis.session.Configuration.newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
>org.apache.ibatis.session.defaults.DefaultSqlSession.DefaultSqlSession(org.apache.ibatis.session.Configuration, org.apache.ibatis.executor.Executor, boolean)
>org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(java.lang.String, java.lang.Object)
>org.apache.ibatis.executor.SimpleExecutor.doQuery
>org.apache.ibatis.executor.statement.PreparedStatementHandler.query
>org.apache.ibatis.executor.resultset.ResultSetWrapper.ResultSetWrapper
至此,mybatis源码执行流程就分析结束了。mybatis的源码没有注释,但是可读性很强。
ps:源码学院-专门为培养bat职员而生,老师讲的很好,知识也很全面,当然还是需要自己努力。详情加群:687721065