从源码和架构层面对mybatis的总结
@[toc]
概述
mybatis是什么?
mybatis是一款半自动化的持久层框架,它封装了JDBC操作,支持定制化SQL,高级映射。但它的数据库无关性较低,2个不同的数据库,可能需要2套SQL语句
mybatis的基本使用?
- 编写全局配置文件
- 编写mapper映射文件
- 加载配置文件,生成SqlSessionFactory
- 创建SqlSession,通过SqlSession调用mapper映射文件中的SQL语句来执行数据库操作
架构流程
三层结构
接口层
使用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实现,是单例模式