文章目錄
# 分層架構圖
# 用戶基本操作
分析基本操作,後面深入瞭解每步操作的源碼流程
-
獲取 sqlSessionFactory 對象
-
獲取 sqlSession 對象
-
獲取接口代理對象(MapperProxy)
-
執行增刪改查方法
文章目錄
# 瞭解源碼 - 目的
爲 mybatis 插件開發做鋪墊
源碼學習過程中
着重注意以下幾個類
- Executor
-
執行器 - ParameterHandler
-
參數處理器 - ResultSetHandler
-
結果集處理器 - StatementHandler
-
SQL語句處理器
文章目錄
# 源碼 - 1: 看 builder ⇒ factory 全流程
在 我們 自己寫的 getSqlSessionFactory
上打一個斷點
build 一進來, 創建 XMLConfigBuilder 用來解析 xml 配置文件
進入到 parse 方法
看到 調用 parseConfiguration ,用來解析配置文件
看看 parseConfiguration 怎麼玩的
可見(上圖),先拿到 configuration 節點(根節點,對應xml文件就能清楚看到)
然後 挨個解析每一個節點信息
settings
properties
typeAliases
plugins
...
然後,再進入 看 settingsElement
每一個 settings 標籤 能進行的設置,都能看到了(下圖) 🐶🐶🐶
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️
🌻🌻🌻🌻🌻🌻🌻🌟🌟🌟🌟🌟🌟🌟🌟🌟
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
同樣的 ,看看 mapperElement
各種判斷,看看 <mapper>
註解上,我們用了什麼方式 引用的 mapper.xml 文件
分析完成後,(綠框) , 調用相應的解析方法
這裏的 parse 是什麼? XPathParser
點進去 看 parse() 方法 , 看怎麼解析 mapper 文件的
(下圖)先拿到 namespace
然後 分析:
parameterMap
resultMap
sql
select / insert / update / delete
進入 解析 增刪改查方法
可以看到,(結合上下面),context 就是我們 的每一條 包含 sql 語句的標籤。
statementParser 就是對這每一條的標籤的 解析類
這個類 調用了 parseStatementNode
方法,點進去看
🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻
😻😻😻😻😻😻😻😻😻
🌹🌹
(下面) 對 select / insert / delete / update
標籤的 各種屬性 的解析
🌹🌹
😻😻😻😻😻😻😻😻😻😻
🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻🍻
拿出 這些標籤的 詳細屬性 幹什麼?
在方法最後,看到 ,
用這些屬性 構造了 一個 MappedStatement ( 下圖 )
自然的 , 我們呢 進去 看 addMapStatement
最終返回的 是一個 封裝好的 statement
🍁🍁🍁🍁🍁🍁🍁🍁🍁
🍃🍃🍃🍃🍃🍃
不難總結出:mybatis配置裏面,一個MappedStatement就代表一個增刪改查標籤的詳細信息
🌴🌴🌴🌴🌴🌴🌴🌴
好,經過這樣 的 各種循環之後,配置的解析就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 (默認) ==》 簡單的執行器
+ RESUSE - 重用 ==》 可以重用的執行器
+ BATCH - 批量 ==》 做批量操作的執行器
(下圖)可見,事務在這裏開啓
關鍵,看 Executor
點進 Executor 接口,定義了 增刪查改 、事務、緩存 三部分的 方法
進入 newExecutor 方法
這裏(下圖),就會根據之前說的 type 不同,創建不同的 Executor
然後 判斷是否開啓二級緩存。
如果開啓, 創建 CachingExecutor
點進去看 CachingExecutor
(下圖) , 構造方法,對傳入的 Executor 進行包裝
實際上,還是 傳入的 Executor 執行 Execute 操作
這樣做的作用?
就 query 查詢爲例,用 CacheExecutor 的 query 會 在 查詢前 查 緩存,如果有,直接返回 查詢結果。
下面看 interceptorChain.pluginAll
攔截器鏈的 插入 全部 插件 方法
可以看到(下圖),就是遍歷所有的 攔截器(interceptor) , 對executor 進行包裝
newExecutor 方法看完。
退回到 外面
(下圖)創建 DefaultSqlSession ,裏面包含了 configuration 和 executor 的信息
最終,返回 DefaultSqlSession
## 總結
獲取 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 時候 見到過
registry 裏面包含了 配置的 mapper 和 對應的 代理 工廠 MapperProxyFactory
進去 mapperRegistry 的 getMapper 看(下圖)
首先,從 registry >> knownMappers 下 獲取 代理工廠(根據接口類型)
(如果拿不到,就報錯)
如果拿到了 ,就獲取實例
進入 newInstance
可以看到 是對 一些 屬性的封裝
- sqlSesson
- mapperInterface - 當前的接口
- methodCache - 當前方法對應的 緩存 map
點進去
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
⭐️⭐️⭐️
這裏 看到 MapperProxy 實現了 InvocationHandler !!!
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
⭐️⭐️⭐️
InvocationHandler
是什麼?
看Proxy
的 newProxyInstance 方法
他這裏說了
invocationHandler 是用來做method dispatch 的
也就是 ,指定代理的增強 具體做什麼
繼續看 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 的 映射集
裏面映射了方法的詳細信息
然後,把映射的 方法詳細信息傳遞給 executor 的 query 查詢方法
executor.query 方法 參數裏面還調用一個方法
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
statement 是 原生jdk 的 statement哦
(下圖)拿到配置信息
下面 是 new statementHandler 對象(四大 對象之一哦)
作用?
可以創建出 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
類似路由器,
在其構造函數中會根據Mapper文件中設置的StatementType來選擇
使用
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