mybatis源码与整体架构总结

441 阅读3分钟

从源码和架构层面对mybatis的总结 @[toc]

概述

mybatis是什么?

mybatis是一款半自动化的持久层框架,它封装了JDBC操作,支持定制化SQL,高级映射。但它的数据库无关性较低,2个不同的数据库,可能需要2套SQL语句

mybatis的基本使用?

  • 编写全局配置文件
  • 编写mapper映射文件
  • 加载配置文件,生成SqlSessionFactory
  • 创建SqlSession,通过SqlSession调用mapper映射文件中的SQL语句来执行数据库操作

架构流程

三层结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LfGIOhPg-1573726013288)(C:\Users\Vergi\Desktop\3level-architech.png)]

接口层

使用SqlSession和Mapper接口,来完成对SQL语句的调用,日常开发中主要接触这一层

数据处理层

这一层是mybatis进行的工作,负责SQL语句组装,查询参数绑定,结果集映射

基础支撑层

这一层可以理解为我们全局配置里的内容。包括数据库连接信息,事务管理信息,配置缓存,编写mapper映射文件中的SQL语句等

工作流程

  • 向SqlSession传入SQL语句的id,以及查询参数
  • 找到待执行的SQL信息,交给Executor执行器处理
  • Executor负责对SQL语句进行组装拼接,后交给StatementHandler处理
  • StatementHandler封装了JDBC的操作,它负责根据SQL信息,生成对应的Statement,并利用ParameterHandler进行查询参数的解析与绑定,后执行查询
  • StatemenHandler查询完毕,将结果集交由ResultSetHandler进行结果集信息的解析与封装处理(参数解析,结果集解析,都会用TypeHandler来做类型转换,java类型与JDBC类型)

源码部分

全局配置文件解析过程

  • 获得配置文件的InputStream,创建Document对象
  • 利用Xpath语法,解析各个配置节点
  • 将信息封装到Configuration对象中,生成SqlSessionFactory

源码过程

SqlSessionFactoryBuilder # build 
 |- XMLConfigBuilder # parse 
   |- XMLConfigBuilder # parseConfiguration

mapper映射文件解析过程

  • 一个mapper.xml映射文件,由namespace属性作为唯一标识
  • 拥有namespace属性的mapper.xml映射文件,会被注册到Configuration中的mapperRegistry中,以便后续生成mapper代理对象
  • 一个mapper.xml,对应一个MapperBuilderAssistant对象,这个builderAssistant对象解析并保存了该mapper.xml中的公共标签,如parameterMap,resultMap,cache,sql,这些标签可能在某个CRUD标签里被使用
  • 解析CRUD标签,即 select | update | insert | delete 标签,一个CRUD标签,被封装成一个MappedStatement对象,以标签的id属性作为唯一标识,MappedStatement里包含了SQL语句信息,参数映射信息,结果集映射信息

源码过程

XMLConfigBuilder # mapperElement
 |- XMLMapperBuilder # parse
   |- XMLMapperBuilder # configurationElement

SQL加载与组装过程

SQL装载

  • 在解析mapper映射文件中的CRUD标签时,对SQL语句进行了解析和封装
  • 将一个CRUD标签,封装成SqlNode,并将其子元素(可能是文本节点,也可能是动态SQL节点),也封装成SqlNode,利用组合模式,对这些SqlNode进行组装,最终将SqlNode和Configuration封装在一起,形成SqlSource
  • 有动态SQL标签的,或者有${} 的,会被封装成DynamicSqlSource,其余的,会被封装成RawSqlSource(在Executor执行时都会解析并封装成StaticSqlSource)
  • SqlSource和其他信息,一起被封装为MapperStatement,一个CRUD标签,对应一个MappedStatement

源码过程

XMLMapperBuilder # buildStatementFromContext
 |- XMLStatementBuilder # parseStatementNode
   |- XMLLanguageDriver # createSqlSource 
     |- XMLScriptBuilder # parseScriptNode

SQL组装

  • 调用Executor进行执行时,会查找对应的MappedStatement,并调用其SqlSource的getBoundSql,进行SQL语句的组装,并封装查询参数
  • 调用getBoundSql方法时,会调用SqlNode的apply方法,不同SqlNode子类,会采取不同方式,解析动态SQL标签,并进行SQL语句拼接,并将#{}替换为 ? ,将${}做字符串拼接,之后封装到StaticSqlSource,此时已经将SQL语句解析并组装,这个StaticSqlSource里就是SQL语句以及查询参数

源码过程

CachingExecutor # query
 |- MappedStatement # getBoundSql
  |- DynamicSqlSource # getBoundSql
   |- SqlNode # apply // 动态SQL的组装,以及将${}进行字符串拼接
   |- SqlSourceBuilder # parse //这里是将#{}替换成 ?
   //组装完成后封装成StaticSqlSource
   //并调用StaticSqlSource的getBoundSql
   //new 一个新的BoundSql,传入组装好的SQL语句,以及查询参数

执行查询过程

  • 从Configuration中根据id,取出一个MappedStatement
  • 将MappedStatement交由Executor处理
  • Executor中调用MappedStatement的getBoundSql,获取组装好的SQL语句,以及查询参数
  • 根据查询参数,MappedStatement等信息,构建出一个StatementHandler出来
  • StatementHandler新建一个Statement对象,并借助ParameterHandler完成对Statement的入参绑定
  • 执行查询,并将结果集交由ResultSetHandler处理

源码过程

DefaultSqlSession # selectList
 |- CachingExecutor # query
   |- BaseExecutor # query
     |- BaseExecutor # queryFromDatabase
       |- SimpleExecutor # doQuery
         |- Configuration # newStatementHandler
         |- SimpleExecutor # prepareStatement
         |- StatementHandler # query

缓存过程

  • 执行查询时,首先是走CachingExecutor,CachingExecutor中检查是否开启二级缓存,若开启,则会负责二级缓存数据的存取
  • 若没开启二级缓存,或二级缓存没命中,进入到BaseExecutor中,尝试从一级缓存中拿数据,若一级缓存中也没有,则会访问数据库
  • 二级缓存是mapper级别的,即一个mapper,对应一个二级缓存。在源码中,二级缓存是被MappedStatement持有。二级缓存是通过mapper映射文件中的<cache/> 标签开启的
  • 一级缓存无法关闭,但可以在全局配置中设置<setting name="localCacheScope" value="STATEMENT"/> 来使其失效(每次执行操作都会清空一级缓存)

源码过程

//二级缓存
CachingExecutor # query
 |- MappedStatement # getCache//获取该mapper下的二级缓存
 |- TransactionalCacheManager # getObject //查找缓存中是否有数据
   |- TransactionalCache # getObject //查找缓存中是否有数据
   
 |-TransactionalCacheManager # putObject //存入二级缓存
   |- TransactionalCache # getObject
   
//若二级缓存未命中,走一级缓存
BaseExecutor # query
  |- PerpetualCache # getObject //从一级缓存中取数据

延迟加载过程

  • 在查询结束后,调用ResultSetHandler对结果集进行处理时,若发现开启了延迟加载,且有嵌套查询,则会对结果生成一个代理对象
  • 当调用结果的get方法,访问延迟加载的数据时,发现数据为空,则获取其MappedStatement,执行一次查询,把查询结果set到主对象中

源码过程

DefaultResultSetHandler # createResultObject
 |- Configuration # getProxyFactory
 |- JavassistProxyFactory # createProxy
 // 默认是使用JavassistProxyFactory

获取Mapper代理过程

  • 调用Configuration中的mapperRegistry来查找这个mapper的类信息,会找到一个MapperProxyFactory对象
  • 使用JDK内置的动态代理,调用java.lang.reflect下的Proxy来生成一个代理类

源码过程

DefaultSqlSession # getMapper
 |- MapperRegistry # getMapper
  |- MapperProxyFactory # newInstance
    |- MapperProxy # 构造函数
    |- Proxy.newProxyInstance

mybatis插件过程

mybatis的插件会对以下几个类起作用

  • Executor
  • StatementHandler
  • ParameterHandler
  • ResultSetHandler

实现Interceptor接口,覆写intercept方法,在intercept方法中完成插件的拦截逻辑。并覆写plugin方法,在plugin方法中调用Plugin.wrap来生成一个代理对象并返回,即可。底层也是使用的JDK动态代理

源码流程

//以Executor为例
DefaultSqlSessionFactory # openSession
  |- Configuration # newExecutor
    |- InterceptorChain # pluginAll

类关系总结

配置文件解析相关

  • BaseBuilder

    • XMLConfigBuilder

    • XMLMapperBuilder

      持有一个MapperBuilderAssistant

    • XMLStatementBuilder

    • XMLScriptBuilder

SQL组装相关

  • SqlSource

    • DynamicSqlSource :

      含有动态SQL,或 ${} ,会被解析封装成这个类

    • RawSqlSource :

      不含动态SQL,以及${} ,会被解析封装成这个类

    • StaticSqlSource

      Executor执行查询,调用getBoundSql方法时,组装好SQL语句,与查询参数一同封装起来,为这个类

  • SqlNode

    • TextSqlNode

      文本节点

    • StaticTextSqlNode

      文本节点,且文本不包含 ${}

    • IfSqlNode

    • ForEachSqlNode

    • ChooseSqlNode

    • TrimSqlNode

      有2个子类,分别是

      • WhereSqlNode
      • SetSqlNode
    • MixedSqlNode

      作为根节点,其有一个List<SqlNode> 字段

执行相关

  • Executor

    • BaseExecutor

      其有3个子类,分别是

      • SimpleExecutor
      • ReuseExecutor
      • BatchExecutor
    • CachingExecutor

  • StatementHandler

    • RoutingStatementHandler

      仅作路由选择功能

    • BaseStatementHandler

      其有3个子类,分别是

      • SimpleStatementHandler
      • PreparedStatementHandler
      • CallableStatementHandler
  • ParameterHandler

  • ResultSetHandler

  • Cache

    • PerpetualCache
    • LruCache
    • FifoCache
    • SerializedCache
    • LoggingCache
    • SynchronizedCache
    • SoftCache
    • WeakCache
    • TransactionalCache

设计模式

各种Builder是 构建者模式

SqlSessionFactory是工厂模式

Executor中BaseExecutor中留了一个钩子方法doQuery,是模板方法模式

CachingExecutor和二级缓存Cache体系是 装饰器模式

Mapper代理对象,以及延迟加载,是代理模式

SqlNode体系是组合模式

ErrorContext记录了每个线程中的错误上下文环境,使用了ThreadLocal实现,是单例模式