乐趣区

Mybatis源码分析

这篇文章我们来深入阅读下 Mybatis 的源码,希望以后可以对底层框架不那么畏惧,学习框架设计中好的思想;


架构原理


架构图

架构流程图

上面这两幅图来源于网络,不过画的很好,基本说明了 Mybatis 的架构流程。

说明:

  1. Mybatis 配置文件

    • SqlMapConfig.xml,此文件作为 mybatis 的全局配置文件,配置了 mybatis 的运行环境等信息。
    • Mapper.xml,此文件作为 mybatis 的 sql 映射文件,文件中配置了操作数据库的 sql 语句。此文件需要在 SqlMapConfig.xml 中加载。
  2. SqlSessionFactory

    • 通过 mybatis 环境等配置信息构造 SqlSessionFactory,即会话工厂。
  3. SqlSession

    • 通过会话工厂创建 sqlSession 即会话,程序员通过 sqlsession 会话接口对数据库进行增删改查操作。
  4. Executor 执行器

    • mybatis 底层自定义了 Executor 执行器接口来具体操作数据库,Executor 接口有两个实现,一个是基本执行器(默认)、一个是缓存执行器,sqlsession 底层是通过 executor 接口操作数据库的。
  5. MappedStatement

    • 它也是 mybatis 一个底层封装对象,它包装了 mybatis 配置信息及 sql 映射信息等。mapper.xml 文件中一个 selectinsertupdatedelete 标签对应一个 Mapped Statement 对象,selectinsertupdatedelete 标签的 id 即是 Mapped statement 的 id。
    • Mapped Statement 对 sql 执行输入参数进行定义,包括 HashMap、基本类型、pojo,Executor 通过 MappedStatement 在执行 sql 前将输入的 java 对象映射至 sql 中,输入参数映射就是 jdbc 编程中对 preparedStatement 设置参数。
    • Mapped Statement 对 sql 执行输出结果进行定义,包括 HashMap、基本类型、pojo,Executor 通过 MappedStatement 在执行 sql 后将输出结果映射至 java 对象中,输出结果映射过程相当于 jdbc 编程中对结果的解析处理过程。

调用流程图

Executor

MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护

StatementHandler

封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将 Statement 结果集转换成 List 集合。

ParameterHandler

负责对用户传递的参数转换成 JDBC Statement 所需要的参数

ResultSetHandler

负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合

TypeHandler

负责 java 数据类型和 jdbc 数据类型之间的映射和转换

SqlSource

负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回 BoundSql 表示动态生成的 SQL 语句以及相应的参数信息

源码解析


加载全局配置文件

  • 找入口:SqlSessionFactoryBuilder#build 方法
SqlSessionFactoryBuilder#build 构建 SqlSessionFactory
    XMLConfigBuilder#parse 全局配置文件解析,封装成 Configuration 对象
        #parseConfiguration 从根路径开始解析,加载的信息设置到 Configuration 对象中
            #mapperElement 解析 mapper 映射文件
                XMLMapperBuilder#parse 具体解析 mapper 映射文件
                    SqlSessionFactoryBuilder#build:创建 SqlSessionFactory 接口的默认实现类
  • 总结
1.SqlSessionFactoryBuilder 创建 SqlsessionFactory 时,需要传入一个 Configuration 对象。2.XMLConfigBuilder 对象会去实例化 Configuration。3.XMLConfigBuilder 对象会去初始化 Configuration 对象。通过 XPathParser 去解析全局配置文件,形成 Document 对象 
    通过 XPathParser 去获取指定节点的 XNode 对象。解析 Xnode 对象的信息,然后封装到 Configuration 对象中 
  • 相关类和接口
|--SqlSessionFactoryBuilder 
|--XMLConfigBuilder 
|--XPathParser 
|--Configuration

加载映射配置文件

  • 找入口:XMLConfigBuilder#mapperElement 方法
XMLConfigBuilder#mapperElement: 解析全局配置文件中的 <mappers> 标签 
    |--XMLMapperBuilder# 构造方法:专门用来解析映射文件的 
        |--XPathParser# 构造方法:|--XPathParser#createDocument():创建 Mapper 映射文件对应的 Document 对象 
            |--MapperBuilderAssistant# 构造方法:用于构建 MappedStatement 对象的 
        |--XMLMapperBuilder#parse():|--XMLMapperBuilder#configurationElement:专门用来解析 mapper 映射文件 
                |--XMLMapperBuilder#buildStatementFromContext:用来创建 MappedStatement 对象的 
                    |--XMLMapperBuilder#buildStatementFromContext 
                        |--XMLStatementBuilder# 构造方法:专门用来解析 MappedStatement 
                        |--XMLStatementBuilder#parseStatementNode: 
                            |--MapperBuilderAssistant#addMappedStatement: 创建 MappedStatement 对象 
                                |--MappedStatement.Builder# 构造方法 
                                |--MappedStatement.Builder#build 方法:创建 MappedStatement 对象,并存储 到 Configuration 对象中 
  • 相关接口和类
|--XMLConfigBuilder 
|--XMLMapperBuilder 
|--XPathParser 
|--MapperBuilderAssistant 
|--XMLStatementBuilder 
|--MappedStatement

SqlSource 创建流程

  • 找入口:XMLLanguageDriver#createSqlSource
XMLLanguageDriver#createSqlSource 创建 SqlSource,解析 SQL,封装 SQL 语句(出参数绑定)和入参信息

​    XMLScriptBuilder 构造函数:初始化动态 SQL 中的节点处理器集合
​        XMLScriptBuilder#parseScriptNode 
​            #parseDynamicTags 解析 select\insert\ update\delete 标签中的 SQL 语句,最终将解析到的 SqlNode 封装到 MixedSqlNode 中的 List 集合中
​            DynamicSqlSource 构造方法:如果 SQL 中包含 ${} 和动态 SQL 语句,则将 SqlNode 封装到 DynamicSqlSource
​            RawSqlSource 构造方法:如果 SQL 中包含 #{},则将 SqlNode 封装到 RawSqlSource 中,并指定 parameterType
​                SqlSourceBuilder#parse
​                    ParameterMappingTokenHandler 构造方法
​                        GenericTokenParser# 构造方法, 指定待分析的 openToken 和 closeToken 并指定处理器
​                            GenericTokenParser#parse 解析 #{}
​                                ParameterMappingTokenHandler#handleToken  处理 token(#{}/${})​                                    #buildParameterMapping 创建 ParameterMapping 对象
​                                StaticSqlSource 构造方法,将解析之后的 sql 信息,封装到 StaticSqlSource 对象 
  • 相关类和接口
|--XMLLanguageDriver 
|--XMLScriptBuilder 
|--SqlSource 
|--SqlSourceBuilder

创建 Mapper 代理对象

  • 找入口:DefaultSqlSession#getMapper
|--DefaultSqlSession#getMapper:获取 Mapper 代理对象 
​    |--Configuration#getMapper:获取 Mapper 代理对象 
​        |--MapperRegistry#getMapper:通过代理对象工厂,获取代理对象 
​            |--MapperProxyFactory#newInstance:调用 JDK 的动态代理方式,创建 Mapper 代理 

SqlSession 执行主流程

  • 找入口:DefaultSqlSession#selectList()
DefaultSqlSession#selectList
​    CachingExecutor#query
​        BaseExecutor#query 委托给 BaseExecutor 执行
​            #queryFromDatabase
​            SimpleExecutor#doQuery  执行查询
​                Configuration#newStatementHandler  创建路由功能的 StatementHandler,根据 MappedStatement 中的 StatementType
​            SimpleExecutor#prepareStatement  设置 PreapreStatement 的参数
​            BaseExecutor#getConnection 获取数据库连接
​                BaseStatementHandler#prepare 创建 Statement PrepareStatement、Statement、CallableStatement)​                PreparedStatementHandler#parameterize 设置参数
​                PreparedStatementHandler#query 执行 SQL 语句(已经设置过参数),并且映射结果集
​                    com.mysql.jdbc.PreparedStatement#execute 调用 JDBC 的 api 执行 Statement
​                        DefaultResultSetHandler#handleResultSets  处理结果集 
  • 相关接口和类
|--DefaultSqlSession 
|--Executor 
    |--CachingExecutor 
    |--BaseExecutor 
    |--SimpleExecutor 
|--StatementHandler 
    |--RoutingStatementHandler 
    |--PreparedStatementHandler 
|--ResultSetHandler 
    |--DefaultResultSetHandler 

BoundSql 获取流程

  • 找入口:MappedStatement#getBoundSql 方法
MappedStatement#getBoundSql
​    DynamicSqlSource#getBoundSql 
​        SqlSourceBuilder#parse 执行解析:将带有 #{} 的 SQL 语句进行解析,然后封装到 StaticSqlSource 中
​            GenericTokenParser  #构造方法,指定待分析的 openToken 和 closeToken,并指定处理器
​                GenericTokenParser#parse 解析 SQL 语句,处理 openToken 和 closeToken 中的内容 
​                    ParameterMappingTokenHandler#handleToken 处理 token(#{}/${})​                        #buildParameterMapping  创建 ParameterMapping 对象
​                            StaticSqlSource# 构造方法:将解析之后的 SQL 信息,封装到 StaticSqlSource
|--RawSqlSource#getBoundSql 
​    |--StaticSqlSource#getBoundSql 
​        |--BoundSql# 构造方法:将解析后的 sql 信息、参数映射信息、入参对象组合到 BoundSql 对象中 

参数映射流程

  • 找入口:其实就是 SqlSession 执行流程中的 PreparedStatementHandler#parameterize

|--PreparedStatementHandler#parameterize:设置 PreparedStatement 的参数 
​    |--DefaultParameterHandler#setParameters:设置参数 
​        |--BaseTypeHandler#setParameter:​            |--xxxTypeHandler#setNonNullParameter: 调用 PreparedStatement 的 setxxx 方法 

处理结果集

  • 找入口:其实就是 SqlSession 执行流程中的 DefaultResultSetHandler#handleResultSets
|--DefaultResultSetHandler#handleResultSets 
​    |--DefaultResultSetHandler#handleResultSet 
​        |--DefaultResultSetHandler#handleRowValues 
​            |--DefaultResultSetHandler#handleRowValuesForSimpleResultMap 
​                |--DefaultResultSetHandler#getRowValue 
​                    |--DefaultResultSetHandler#createResultObject:创建映射结果对象 
​                    |--DefaultResultSetHandler#applyAutomaticMappings 
​                    |--DefaultResultSetHandler#applyPropertyMappings 

基本上 Mybatis 的流程就是这样了,其中还有很多实现细节暂时看不太懂。我认为学习框架源码分为两步:

  1. 抓住主线,掌握框架的原理和流程;
  2. 理解了处理思路之后,再去理解面向对象思想和设计模式的用法;

目前第一步尚有问题,需要多走几遍源码,加深下理解,一起加油~~

退出移动版