文章目录
# 分层架构图
# 用户基本操作
分析基本操作,后面深入了解<mark>每步操作的源码流程</mark>
-
获取 sqlSessionFactory 对象
-
获取 sqlSession 对象
-
获取接口***对象(MapperProxy)
-
执行增删改查方法
文章目录
# 了解源码 - 目的
<mark>为 mybatis 插件开发做铺垫</mark>
源码学习过程中
着重注意以下几个类
- Executor
-
<mark>执行器</mark> - ParameterHandler
-
<mark>参数处理器</mark> - ResultSetHandler
-
<mark>结果集处理器</mark> - StatementHandler
-
<mark>SQL语句处理器</mark>
文章目录
# 源码 - 1: 看 builder ⇒ factory 全流程
在 我们 自己写的 getSqlSessionFactory
上打一个断点
build 一进来, 创建 XMLConfigBuilder 用来解析 xml 配置文件
进入到 parse 方法
看到 调用 parseConfiguration ,用来解析配置文件
看看 parseConfiguration 怎么玩的
可见(上图),先拿到 configuration 节点(根节点,对应xml文件就能清楚看到)
然后 挨个解析每一个<mark>节点信息</mark>
settings
properties
typeAliases
plugins
...
然后,再进入 看 settingsElement
每一个 settings 标签 能进行的设置,都能看到了(下图) 🐶🐶🐶
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️
🌻🌻🌻🌻🌻🌻🌻🌟🌟🌟🌟🌟🌟🌟🌟🌟
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
同样的 ,看看 mapperElement
各种判断,看看 <mapper>
注解上,我们用了什么方式 引用的 mapper.xml 文件
分析完成后,(绿框) , 调用相应的解析方法
这里的 parse 是什么? XPathParser
点进去 看 parse() 方法 , <mark>看怎么解析 mapper 文件的</mark>
(下图)先拿到 namespace
然后 分析:
parameterMap
resultMap
sql
select / insert / update / delete
进入 解析 增删改查方法
可以看到,(结合上下面),context 就是我们 的每一条 包含 sql 语句的标签。
statementParser 就是对这每一条的标签的 解析类
这个类 调用了 parseStatementNode
方法,点进去看
🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻
😻😻😻😻😻😻😻😻😻
🌹🌹
(下面) 对 select / insert / delete / update
标签的 各种属性 的解析
🌹🌹
😻😻😻😻😻😻😻😻😻😻
🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻
拿出 这些标签的 详细属性 干什么?
在方法最后,看到 ,
用这些属性 构造了 一个 MappedStatement ( 下图 )
自然的 , 我们呢 进去 看 addMapStatement
最终返回的 是一个 封装好的 statement
🍁🍁🍁🍁🍁🍁🍁🍁🍁
🍃🍃🍃🍃🍃🍃
不难总结出:mybatis配置里面,<mark>一个MappedStatement就代表一个增删改查标签的详细信息</mark>
🌴🌴🌴🌴🌴🌴🌴🌴
好,经过这样 的 各种循环之后,配置的解析就ok了。
回到 XMLConfigBuilder的 parse 方法,看返回 的 configuration
里面包含 了 所有
。。。
接上面
。。。
这里 还有 一个重要的 属性
mapperRegistry
(下图)可以看到,这个属性记录的 每个 mapper 都有一个对应的 ***工厂
再 return 会 builder
builder 用 这个 configuration 最终创建 工厂实例
## 总结
根据配置,创建SQLSessionFactory
(把配置文件的信息解析并保存在 Configuration 对象中,返回 DefaultSqlSession 对象)
文章目录
# 源码 - 2 :factory ⇒ session 全流程
上面 看到了 SqlSessionFactory 的获取过程
下面 看 openSession , 看 factory 获取 session 的全过程
这里,sqlsessionFactory 的 实现类 是 DefaultSqlSessionFactory
opensession() 方法里面 调用的 是 openSessionFromDataSource ,
这里 传进去的 第一个参数 是 configuration.getDefaultExecutorType()
(执行器的类型)
值是
SIMPLE
, 什么意思?
官方文档有讲,
值有三
+ SIMPLE (默认) ==》 <mark>简单的执行器</mark>
+ RESUSE - 重用 ==》 <mark>可以重用的执行器</mark>
+ BATCH - 批量 ==》 <mark>做批量操作的执行器</mark>
(下图)可见,事务在这里开启
关键,看 Executor
点进 Executor 接口,定义了 增删查改 、事务、缓存 三部分的 方法
进入 newExecutor 方法
这里(下图),就会根据之前说的 type 不同,创建不同的 Executor
然后 判断是否开启二级缓存。
如果开启, 创建 CachingExecutor
点进去看 CachingExecutor
(下图) , 构造方法,对传入的 Executor 进行包装
实际上,还是 传入的 Executor 执行 Execute 操作
这样做的作用?
就 query 查询为例,用 CacheExecutor 的 query 会 在 查询前 查 缓存,如果有,直接返回 查询结果。
下面看 interceptorChain.pluginAll
***链的 插入 全部 插件 方法
可以看到(下图),就是遍历所有的 ***(interceptor) , 对executor 进行包装
newExecutor 方法看完。
退回到 外面
(下图)创建 DefaultSqlSession ,里面包含了 configuration 和 executor 的信息
<mark>最终,返回 DefaultSqlSession</mark>
## 总结
获取 SqlSession 对象
返回一个 DefaultSqlSession 对象,包含 Executor 和 Configuration
这一步会创建 Executor
文章目录
# 源码 - 3 :session ⇒ mapper 全流程
DefaulttSqlSession 的 getMapper 调用的是 configuration
的 getMapper
两个参数
type 是 mapper 接口 的类型,调用的 用户指定
this 是 调用 configuration 的 DefaultSqlSession
进入 configuration 的 getMapper 方法
可以看到(下图), configuration 调用的 是 mapperRegistry 的 getMapper
方法
雷丰阳
mapperRegistry
是什么?
configuration 的 一个属性
其实之前 也见过
讲 获取 SqlSessionFactoryBuilder 时候 见到过
<mark>registry 里面包含了 配置的 mapper 和 对应的 *** 工厂 MapperProxyFactory</mark>
进去 mapperRegistry 的 getMapper 看(下图)
<mark>首先,从 registry >> knownMappers 下 获取 ***工厂</mark>(根据接口类型)
(如果拿不到,就报错)
如果拿到了 ,就获取实例
进入 newInstance
可以看到 是对 一些 属性的封装
- sqlSesson
- mapperInterface - 当前的接口
- methodCache - 当前方法对应的 缓存 map
点进去
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
⭐️⭐️⭐️
<mark>这里 看到 MapperProxy 实现了 InvocationHandler !!!</mark>
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
⭐️⭐️⭐️
InvocationHandler
是什么?
看Proxy
的 newProxyInstance 方法
他这里说了
<mark>invocationHandler 是用来做method dispatch 的</mark>
也就是 ,指定***的增强 具体做什么
继续看 newInstance,(下图)
其实 , 就是调用了 java.lang.reflect.Proxy
的 newProxyInstance ,创建一个 ***对象。
(其中,传入 mapperProxy 指定 ***的具体操作)
最后,创建出来的 ***对象,被传出去 给用户使用
## 总结
获取接口的***对象(MapperProxy)
getMapper 使用 MapperProxyFactory 创建一个 MapperProxy 的***对象
***对象里面包含了, DefaultSqlSession (这里 包含了 执行增删改查的 Executor)
文章目录
# 源码 - 4 : mapper ⇒ 查询
(下图)看是不是 jdk 默认的方法
如果不是 默认的方法(是用户指定的方法) , 就把 method 封装成 MapperMethod (mapper 能认识的 method 类)
然后 execute 执行方法
然后 进入 execute 方法
- 这里的源码,在 《MyBatis - 4 - 【奥义】映射文件》 这篇文章 也有提及
(下图) 判断 是
- insert
- update
- delete
- select
的哪一种
以 select 为例
根据返回值类型的不同 ,调用不同 方法 获取 result 值
如果 只返回一个 对象,就会来到最后的判断
首先 ,执行 method.convertArgsToSqlCommandParam 分析 参数 , 获得 param
(下图)如果参数时单个,返回单个,如果参数时多个,包装成一个 map 返回
返回到 execute 方法,下一步 执行 sqlSession 的 selectOne (下一步)
上面一直看下来,我们知道 sqlsession 的 实现类是 defaultSqlSession
看 defaultSqlSession 的 selectOne(下图)
(上图)可以看到,尽管是 selectone ,调用的 还是 selectList (查询多个)
看 selectList 是怎么查询的
(上图) 先从 configuration 里面 拿到 statement 的 映射集
<mark>里面映射了方法的详细信息</mark>
然后,把映射的 方法详细信息传递给 executor 的 query 查询方法
<mark>executor.query</mark> 方法 参数里面还调用一个方法
wrapCollection
分析传入参数 是 集合的情况
点进去 看看
再看 query
(上图)getBoundSql 获取绑定的 sql
然后 后面 算出 cache 的key
key: hashCode + 方法id + sql + 参数 xxx 等。。。
特别长 ↓
然后是 重写 的 query (下图)
判断有没有缓存,
有 就 缓存取值 直接返回
没有,就 调用 delegate 的 query 方法
(上图) executor 的 实例 这里是 SimpleExecutor (具体是什么,在前面说了,根据配置创建)
所以,这里,就点进 SimpleExecutor 的 query 方法
(我们发现,SimpleExecutor 继承 BaseExecutor , query 在 BaseExecutor 中 就已经写好了)
可以看到,这里是会 尝试 从一级缓冲(localCache)中取数据
(上图)但是,因为是第一次查,所以 没有数据
我们进入 queryFromDatabase
首先,在缓存中 在 命名空间中 先 放一个占位符(下面)
再 查到数据 (list) 之后,再放list入缓存
然后是 , doQuery ,下面 看看 传入的 参数
ms mappedStatement 这次查询的 详细 标签信息
参数
rowBounds 做逻辑分页(不分页 下面就默认值)
resultHandler 结果处理
boundSQL 就是 sql 语句
点进去 doQuery
<mark>statement 是 原生jdk 的 statement哦</mark>
(下图)拿到配置信息
<mark>下面 是 new statementHandler 对象(四大 对象之一哦)</mark>
作用?
可以创建出 statement 对象。
(下图) 首先 new 一个 RoutingStatementHandler
看 RoutingStatementHandler 的构造 (下图)
判断 statementType , 创建响应的 statementHandler
StatementType 是在哪里定义的?
mapper.xml 里面
默认值? Prepared
所以 这里 new 的是 默认的 PrepareStatementHandler (下图)
调用的 PreparedStatementHandler
父类 BaseStatementHandler
(下图)
BaseStatementHandler 的构造 对传入 参数 进行封装 (下图)
参数:
- executor - 执行器
- mappedStatement - 详细信息(最强)
- parameterObject - 参数
- rowBounds - 分页信息(这里没用)
- resultHandler - 结果处理器
- boundSql - sql 语句
(上图) 需要留意,这里new 了两个 处理器
一个 是 参数的
一个是 结果的
注意: 两个末尾都调用了 pluginAll
(下图) 然后 , 调用 interceptorChain 的 所以插件 方法 pluginAll (这和 前面的 executor 一样)
然后 继续返回
然后用 prepareStatement 创建 jdk 原生的 statement 对象 (下图)
创建 原生 statement 的过程也很简单(下图)
(下图) 打开 连接
通过 handler 的 prepare 方法 创建 statement
(下图) handler.parameterize
参数预编译赋值
预编译 sql
产生 PreparedStatement
对象(下图)
RoutingStatementHandler干嘛的??
RoutingStatementHandler
类似路由器,
在<mark>其构造函数中会根据Mapper文件中设置的StatementType来选择</mark>
使用
SimpleStatementHandler、
PreparedStatementHandler
或是CallableStatementHandler
。
其实现的接口StatementHandler的方法也是由这三个具体实现类来实现。
《Mybatis源码之Statement处理器RoutingStatementHandler(三)》
到 RoutingStatementHandler 的 parameterize 方法(下面)
Routing 路由
注意:这里的 delegate 是 preparedStatementHandler
在之前的代码 看到 ,选择了 preparedStatement
再 进去 parameterize
这里的 parameterHandler 是在 构造 BaseStatementHandler 时候 赋值的
然后,setParameters (这里是 DefaultParameterHandler)
setParameters 干嘛?
之前不是预编译了嘛。
这里就把 预编译时候的 “?” 填上值
首先,是获取 参数映射
对参数值 做获取(下图)
typeHandler 根据 类型 对 参数值 和参数名做一一映射(下图)
类型解析处理器(下图)
这里,(我们的po属性就是 long) (下图)
这里,就有专门的 long 类型的处理器(下图)
设置好了,就一直返回
(这里 logger 是因为 我开了 aop 和 日志)
继续看 handler 的 query 方法(下图)
(前面三个代码1、准备配置,2、准备处理器,3、准备statement 和预编译。现在是真的查了!!!)
点进去
preparedStatement 执行 execute 方法(这里 是 java.sql.PreparedStatement
)
(完)
另外,还可以看看 execute 的调用
## 总结
总结的流程如下:
- 根据配置文件(全局,sql映射)初始化出
Configuration
对象 - 创建一个
DefaultSqlSession
对象
他里面包含Configuration
以及Executor
(根据 全局 配置文件中的 defaultExecutorType 创建出对应的 Executor ) DefaultSqlSession.getMapper()
拿到 Mapper 接口对应的 MapperProxyMapperProxy
里面 有 (DefaultSqlSession)- 执行增删改查方法
- 调用
DefaultSqlSession
的增删改查 (Executor) - ·会创建一个
StatementHandler
对象。
(同时也会创建ParameterHandler
和ResultSetHandler
) - 调用
StatementHandler
的预编译策略和 参数设置值 (使用ParameterHandler
) - 调用
StatementHandler
的 增删改查 ResultSetHandler
封装 结果
- 调用
即:
***对象 =》 DefaultSqlSession =》 Executor =》 StatementHandler