mybatis源码解析
架构流程图
说明:
-
mybatis配置文件
SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信
息。
Mapper.xml,此文件作为mybatis的sql映射文件,文件中配置了操作数据库的sql语句。此
文件需要在SqlMapConfig.xml中加载。 -
Configuartion對象
封裝了Mybatis整个的配置数据(全局配置文件和所有映射文件),这个对象不断向下传递
-
SqlSessionFactory
通过mybatis环境等配置信息构造SqlSessionFactory,即会话工厂。SqlSessionFatory加载Configuartion。往下产生SqlSession。SqlSessionFactory由SqlSessionFactoryBuilder(构建者模式)产生,加载配置文件,将数据封装到Configuartion对象中,创建SqlSessionFatory,将Configuartion对象传递给SqlSessionFactory对象。 -
sqlSession
通过会话工厂创建sqlSession即会话,程序员通过sqlsession会话接口对数据库进行增删改查操作。一次SQL调用。Session通过SqlSessionFactory工厂产生。 -
Executor执行器
mybatis底层自定义了Executor执行器接口来具体操作数据库,Executor接口有两个实现,一个是基本执行器(默认)、一个是缓存执行器,sqlsession底层是通过executor接口操作数据库的。执行JDBC代码,Configuration传递过来。
-
MappedStatement
它也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个select\insert\update\delete标签对应一个Mapped Statement对象,select\insert\update\delete标签的id即是Mapped statement的id。MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
大白话:映射文件中一个CRUD(select、insert等)标签对应的数据对象,所以MappedStatement对象中会封装Sql语句、参数类型、输出结果类型、statement类型。JDBC中有三种statement对象,statement、PreparedStatement、CallableStatement
类图
-
SqlNode
解析所有的SqlNode信息。举例:就是把xml一个select中的Sql信息放入不同的SqlNode中
-
IfNode
解析xml中带if标签的
-
TextSqlNode
解析文本SQL,带有#{},${} 举例:select * from user where id=#{id}
-
staticTestSqlNode
解析纯文本SQL 举例:select * from user where id=10
-
MixedSqlNode
以集合的方式存储所有的SqlNode,意思就是 ifNode、TextSqlNode、staticTextSqlNode都以集合的方式存在MixedSqlNode中
-
SqlSource
提供Sql信息的存储和解析功能
-
DynamicSqlSource
封装了带有动态标签获取${}信息的SQL语句,也有带有#{}的
-
RawSqlSource
封装了只包含#{}信息的SQL语句
-
StaticSqlSource
封装了RawSqlSource,通过SqlSource可以获取BoundSQL(存储解析之后的SQL语句)
10.BoundSql
封装解析之后的SQL信息,以及解析占位符?时产生的参数信息
11.TokenHandler
解析#{},${}的interface
12.ParameterMappingTokenHandler
将sql#{}替换为占位符?,将id=#{} 中的 id参数名存储到list集合中,做对象映射
13.GenericTokenParser
通用处理解析sql,解析解析${}和#{},返回sql语句
14.XMLConfigBuilder
专门用来解析全局配置文件的解析器
15.XMLMapperBuilder
专门用来解析映射文件的解析器
16.SimpleTypeRegistry
储存基本类型的,在设置参数时判断是否是基本类型
17.ParameterMapping
从#{}中解析出来的参数信息,包括参数名称和类型
18.MappedStatement
用来封装映射文件中的CRUD标签脚本内容,比如select标签
手写Mybatis流程
简单流程
SQL解析流程,不是一边执行,一遍解析,而是需要一次性的完成配置文件,将所有解析出来的数据封装到Configuartion对象中。
SQL解析流程(从配置文件中获取JDBC需要的数据信息)
1.完成全局配置文件的读取和解析工作,最终将解析出来的信息,封装到【Configuration】对象中
a)运行时环境信息,其实在此指的就是DataSource的配置信息,将DataSource封装到Configuration对象中存储。
b)在加载全局配置文件的时候,就会触发映射文件的加载。
c)映射文件的加载,先去针对每个select标签进行解析,获取【id值(statementId),SQL语句、参数类型、结果类型、statement类型】
最终将解析出来的信息,封装到【MappedStatement】对象,
将该对象封装Map集合中,key为statementId,value就是该对象,然后就集合存储到【Configuration】对象中
d)MappedStatement对象中存储Sql信息,是通过【SqlSource】进行存储的。
SqlSource对象,不只是存储Sql信息,而且还提供对存储的SQL信息进行处理的功能。
e)SqlSource是通过一个SqlNode集合数据来封装的SQL信息。
SQL执行流程(使用JDBC代码,加上从配置文件中读取的SQL相关信息,就可以完成CRUD的执行流程)
【获取数据源对象(dirvername、URL、username、password)提升创建Connection的性能】
【获取Configuartion对象,从该对象中获取DataSource对象】
【有了DataSource对象,从该对象中获取Conneciton对象】
【获取JDBC可以执行的SQL语句,此处获取的是BoundSql,此时调用的就是【SQLSource】的SQL解析功能】
【从BoundSql中获取SQL语句,BoundSql中拿出来的就是解析后的SQL,加上#{}前面的参数名】
【从MappedStatement对象中获取statement类型,simple、prepared、callable】
【根据不同的statement去创建不同的statement对象】
preparedStatement = connection.prepareStatement(sql);
【从BoundSql中获取参数集合信息List<ParamterMapping>】
【遍历给参数赋值、先需要读取ParamterMapping中的属性名称从入参对象中获取执行属性的值】
【调用JDBC代码,完成属性的赋值】
【执行statement,并获取ResultSet】
// 向数据库发出 sql 执行查询,查询出结果集
rs = preparedStatement.executeQuery();
【从MappedStatement对象中获取输出结果类型、也就是结果要映射的类型】
【遍历结果集、获取结果集中每一行的数据】
【获取结果集中每一行每一列的数据,获取列的名称】
【根据列的名称、通过反射、去设置要映射的java类型的指定属性值】
// 遍历查询结果集
while (rs.next()) {
System.out.println(rs.getString("id") + " " + rs.getString("username"));
}
细化流程
a) 加载配置文件(XML如何配置,直接参考mybatis的配置方式)
* 【解析全局配置文件】
** 数据源信息(dirvername、URL、username、password) --最终解析出DataSource对象,并封装到COnfiguartion对象中。
** 映射文件的信息
* 【解析映射文件】SqlSource和SqlNode,都是为了去存储数据,并且对存储的数据提供了一些操作。
** 解析select标签(statement标签) ---封装【MappedStatement】对象,传入id、parameterType、ResultType、statementType
** 解析select标签中的SQL信息(script)---封装成[SQLSource对象]----》【SQLNode】
*** DynamicSqlSource和RawSqlSource区别:
**** DynamicSqlSource的SqlNode集合信息【解析】工作是发生在【每一次调用getBoundSql方法的时候】,另外DynamicSqlSource解析的是带${}的。
**** RawSqlSource(#{}替换为?占位符)的SQLNode集合信息【解析】工作是发生在【第一次构造RawSqlSource的时候】,只需要被解析一次
b) JDBC执行代码(如果需要配置文件的信息,需要从【Configuartion】对象中获取)
* 【获取连接】
** 使用数据源对象去优化连接的创建(DBCP,需要添加第三方的依赖),并且将该信息配置到XML文件中
** 需要通过Configuration对象,去获取解析出来的【DataSource】对象信息
* 【获取SQL】
** 拼接SQL语句:将所有的【SqlNode】(【SqlSource】--->SqlNode集合)中保存的信息,都【拼接】到StringBuffer对象中。在拼接的过程中,会将【${}】解决掉。
*** SqlNode中包含SqlNode,SqlNode中包含SqlNode集合
** 解析SQL语句:将完成的SQL语句中的【#{}】处理掉,在解析之后,会形成两部分数据【最终的SQL语句、#{}中的属性名称】,将信息封装到StaticSqlSource中
** 通过SqlSource获取BoundSql(jdbc可执行的SQl语句,参数信息集合)
** 通过【BoundSql】,获取里面存储的SQL语句
*** 【BoundSql】将最终的【SQL语句】和【该SQL语句中解析出来的参数信息】,绑定到一起,方便后边一起使用。
select * from user where id =#{id} and name = #{name}
BoundSql(
select * from user where id = ? and name = ?
List<ParameterMapping>
id和name这两个【参数名称】
除了参数名称,还需要封装【参数类型】
)
* 【创建Statement】
** 通过【MappedStatement】对象获取statementType
* 【设置参数】
** 通过【MappedStatement】对象获取入参类型(简单类型、引用类型)
** 如果是引用类型,则需要SQL解析过程中,产生的参数信息(ParameterMapping集合信息),我要根据这个参数信息,去入参对象获取指定属性值
** 调用statment.setString(1,"zhangsan")
* 【执行Statement】
* 【处理结果】
** 通过【MappedStatement】对象输出映射类型
** 通过反射给输出映射类型对应的对象,去设置属性值(通过resultSet结果集中的每一列中来)
源码阅读
解析阶段
【SqlSessionFactoryBuilder】
|–【XMLConfigBuilder】#new
|–【XMLConfigBuilder】#parse
|–parseConfiguration
|—xxxxxx
|—environmentsElement
|—mapperElement
|–【XMLMapperBuilder】#new
|–【XMLMapperBuilder】#parse
|–configurationElement
|–xxxxxx
|–buildStatementFromContext
|–【XMLStatementBuilder】#new
|–【XMLStatementBuilder】#parseStatementNode
|–【XMLLanguageDriver】#createSqlSource
|–【XMLScriptBuilder】#new
|–【XMLScriptBuilder】#parseScriptNode
|—parseDynamicTags
|—DynamicSqlSource#new
|—RawSqlSource#new
|–【MapperBuilderAssistant】#addMappedStatement:封装MappedStatement
【SqlSessionFactoryBuilder】:工厂构建类
|--【XMLConfigBuilder】#new 全局配置文件解析类 ,创建了Configuartion对象, 并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名
|--【XMLConfigBuilder】#parse 使用XPATH解析XML配置文件,将配置文件封装为Configuration对象,返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
|--parseConfiguration: 从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
|--environmentsElement:解析</environments>标签 将数据源放入DataSourceFactory接口中,实现类有三个,
*UnpooledDataSource:非数据库连接池的数据源,每次获取数据库Connection对象都会创建,不会使用数据库连接池
*PooledDataSource:数据库连接池数据源
*JndiDataSource:通过容器获取数据源
将DataSourceFactory中的datasource放入Environment构建者类中,最后将id,transactionFactory,dataSource放入全局Configuartion中。
|--mapperElement 解析</mappers>标签
|--【XMLMapperBuilder】#new 用来解析mapper映射类
|--【XMLMapperBuilder】#parse 通过XMLMapperBuilder解析mapper映射文件
|--configurationElement 从映射文件中的<mapper>根标签开始解析,直到完整的解析完毕
|--buildStatementFromContext 解析<select>\<insert>\<update>\<delete>子标签
|--【XMLStatementBuilder】#new MappedStatement解析器
|--【XMLStatementBuilder】#parseStatementNode 解析select等4个标签,创建MappedStatement对象
|--【XMLLanguageDriver】#createSqlSource 创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
|--【XMLScriptBuilder】#new 初始化了动态SQL标签处理器
|--【XMLScriptBuilder】#parseScriptNode // 解析动态SQL
|---parseDynamicTags 将动态SQL标签中的SQL信息分别封装到不同的SqlNode中,NodeHandler 使用了策略模式
|---TextSqlNode 解析文本SQL,带有#{},${}
|---StaticTextSqlNode 存入纯文本sql
|---IfNode 带有if标签的
|---xxxx
|---DynamicSqlSource#new 封装了带有动态标签获取${}信息的SQL语句,也有带有#{}的
|---RawSqlSource#new 封装了只包含#{}信息的SQL语句
|--【MapperBuilderAssistant】#addMappedStatement:封装MappedStatement 将MappedStatement对象存储到Configuration中的Map集合中,key为statement的id,value为MappedStatement对象
|-- build 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
创建sqlSession
【DefaultSqlSessionFactory】#openSession
|–openSessionFromDataSource
|—getEnvironment
|—getTransactionFactoryFromEnvironment
|—transactionFactory.newTransaction
|–configuration.newExecutor
|–DefaultSqlSession#new
【DefaultSqlSessionFactory】#openSession
|--openSessionFromDataSource 获取数据源环境对象
|---getEnvironment 获取事物工厂
|---getTransactionFactoryFromEnvironment 获取JdbcTransaction或者ManagedTransaction
|---transactionFactory.newTransaction 创建Executor执行器
|--configuration.newExecutor 创建Executor执行器
|--DefaultSqlSession#new 创建DefaultSqlSession
selectOne跟踪
SqlSession#selectOne
|–selectList
|–CachingExecutor#query
|–BaseExecutor#query
|–queryFromDatabase
|–SimpleExecutor#doQuery
|-- Configuration configuration = ms.getConfiguration(); 获取Configuartion对象
|-- prepareStatement(handler, ms.getStatementLog()); 设置参数
|-- 【DefaultParameterHandler】#setParameters 设置参数映射
|-- 【StatementHandler】.query(stmt, resultHandler); 执行SQL语句(已经设置过参数),并且映射结果
|–【PreparedStatementHandler】#query 执行PreparedStatement,也就是执行SQL语句 处理结果集
|–DefaultResultSetHandler#createResultObject
SqlSession#selectOne
|--selectList
|--CachingExecutor#query RowBounds是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页), wrapCollection(parameter)是用来装饰集合或者数组参数
|--CachingExecutor#query 先查询CachingExecutor二级缓存 二级缓存没有 查询BaseExecutor一级缓存,没有从queryFromDatabase数据库查询
|--queryFromDatabase 从数据库查询
|--【SimpleExecutor】#doQuery 执行查询
|-- Configuration configuration = ms.getConfiguration(); 获取Configuartion对象
|-- prepareStatement(handler, ms.getStatementLog()); 设置参数
|-- 【DefaultParameterHandler】#setParameters 设置参数映射
|-- 【StatementHandler】.query(stmt, resultHandler); 执行SQL语句(已经设置过参数),并且映射结果集
|--【PreparedStatementHandler】#query 执行PreparedStatement,也就是执行SQL语句 处理结果集
|--ResultSetHandler#handleResultSets 处理结果集
|--DefaultResultSetHandler#createResultObject 创建要映射的PO类对象