Mybatis的核心架构及源码解读
作者:抠脚的大灰狼
这篇文章主要介绍了Mybatis的核心架构及源码解读,mybatis是一款半自动化的持久层框架,它封装了JDBC操作,支持定制化SQL,高级映射,但它的数据库无关性较低,需要的朋友可以参考下
概述
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语句,与查询参数一同封装起来,为这个类
- DynamicSqlSource :
- 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
到此这篇关于Mybatis的核心架构及源码解读的文章就介绍到这了,更多相关Mybatis的核心架构内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!