Mybatis中SqlSource解析流程詳解

2020-12-12 IT樂知

前面幾篇文章都在詳細分析mapper的加載過程,但是始終沒有看到sql的解析過程,今天來詳細分析下。

解析sql的位置

前面分析到不管是通過註解還是通過xml方式生成mapper,最終都是調用MapperBuilderAssistant類的addMappedStatement方法,這個方法接受的其中一個SqlSource參數,SqlSource類中就是XML文件或者註解方法中映射語句的實現

那麼SqlSource對象是在哪裡創建的呢?

在通過註解實現mapper的流程中是在MapperAnnotationBuilder類的parseStatement方法中對SqlSource進行初始化,初始化代碼如下圖:

通過xml文件實現mapper的流程中是在XMLStatementBuilder類的parseStatementNode方法中對SqlSource進行初始化,初始化代碼如下圖:

可以看到創建SqlSource對象都是通過LanguageDriver實現的,翻譯過來叫做語言驅動,它是一個接口,通過上面源碼可以看出來我們可以自己實現這個接口,並且可以指定使用哪個語言驅動。今天我們只關注mybatis自帶的一個實現XMLLanguageDriver。

XMLLanguageDriver介紹

XMLLanguageDriver類有重載了兩個createSqlSource方法,主要區別在於第二個參數script,從前面兩張源碼圖中可以知道接受XNode類型的script是在解析xml時使用,接受String類型的script是在解析註解時使用,今天只解析接受XNode類型的方法。

這個方法比較簡單只有兩步:初始化一個XMLScriptBuilder對象,執行XMLScriptBuilder對象的parseScriptNode方法。所以重點來到XMLScriptBuilder這個類。

XMLScriptBuilder詳解

XMLScriptBuilder類部分源碼如下圖:

XMLScriptBuilder的初始化比較簡單,要記住XNode context對應的是xml中的一個select、update等節點,在最後調用了initNodeHandlerMap方法設置了select、update等節點子節點對應的處理器。

接著是parseScriptNode方法,可以看到parseScriptNode方法調用了parseDynamicTags方法生成了一個MixedSqlNode對象,然後根據屬性isDynamic判斷創建對應的SqlSource對象。

所以最終要看parseScriptNode方法,同時可以判斷isDynamic這個屬性肯定也會在這個方法中發生變化,parseScriptNode方法的源碼如下圖:

parseDynamicTags方法解析節點下面所有子節點進行遍歷,如果節點是文本節點這解析裡面的內容生成SqlNode(這裡是TextSqlNode或者StaticTextSqlNode)對象放到contents集合中。

如果是腳本節點比如where、if等就調用初始化時保存的節點處理器,如上圖的ForEachHandler、IfHandler處理器,這些對象的處理方法handleNode的第一行代碼又在調用parseDynamicTags方法,就像是一種遞歸。所以我們可以得出xml中的where、if、foreach這些節點時可以彼此包含的,解析時再進行遞歸解析

當然要想調用parseDynamicTags方法,這些對象都是屬於當前類XMLScriptBuilder的內部類。

大的方向梳理了我們再來看這個方法到底在幹什麼,首先這個方法會收集SqlNode對象放到contents集合中,最後把contents作為參數生成MixedSqlNode對象。在處理的過程中如果遇到if、foreach等節點還會把contents傳遞進去,從上面的圖中可以看到ForEachHandler、IfHandler處理器也會調用parseDynamicTags方法生成MixedSqlNode然後再生成對應的SqlNode放到contents中。

所以最終來到兩個關鍵類MixedSqlNode、SqlNode,當然SqlNode肯定有各種子類。

MixedSqlNode與SqlNode

那麼MixedSqlNode與SqlNode是什麼樣子的呢?又是如何組合的?具體源碼如下圖:

可以看到SqlNode是一個接口,而MixedSqlNode只是他的一種實現類,同時來貼出來了靜態文本處理的類和if節點對應的IfSqlNode類,還有一些其他比如WhereSqlNode、ForEachSqlNode等就不再列出來了。

每一種實現類的初始化都比較簡單,比如StaticTextSqlNode是保存一段文本,IfSqlNode保存了if節點的test屬性對應的值和從if節點下解析出來的MixedSqlNode節點。

而他們有一個由SqlNode規定的apply方法,這才是他們正真的作用所在,比如MixedSqlNode是遍歷所有節點執行對應的apply方法,StaticTextSqlNode就只是把對應sql拼接到後面,IfSqlNode是在進行判斷後調用MixedSqlNode去執行if節點下所有的節點。

至於參數DynamicContext後面在調用的時候會分析的。

現在回到XMLScriptBuilder的parseScriptNode方法,方法在執行了parseDynamicTags方法後根據isDynamic屬性初始化了SqlSource,對應類結構圖如下圖:

我們主要關注DynamicSqlSource這個類,可以看到它就兩個屬性configuration、rootSqlNode,分別是全局配置和剛剛分析的MixedSqlNode,也可以從他的getBoundSql方法中看到後面對rootSqlNode的使用,這個留著後面分析。

總結

在configuration中有一個map屬性mappedStatements,他保存著MappedStatement對象,每個MappedStatement對象對應一個sql的所有信息,而MappedStatement也有一個屬性SqlSource,通過SqlSource能夠獲取到對應的sql,而sql的解析時依靠SqlNode。

今天的重點就在於生成SqlNode的過程,它是通過XMLScriptBuilder類是處理xml中crud節點生成。

這裡有意思的是設計時是XMLScriptBuilder自帶處理節點的方法parseDynamicTags生成需要的MixedSqlNode,而在parseDynamicTags方法內部可能會調用內部類WhereHandler、IfHandler的handleNode方法生成對應的SqlNode,而在這些handleNode方法中第一步就是調用parseDynamicTags去生成MixedSqlNode,根據MixedSqlNode生成對應的SqlNode。通過這種遞歸實現了節點多層嵌套的解析

第二個有意思的點在於SqlNode的框架設計,MixedSqlNode存儲一個SqlNode的集合,而具體的比如IfSqlNode又可以持有SqlNode,通過這樣實現,每種SqlNode都可以擁有所有各種的SqlNode功能,但是他們有擁有自己獨立的特點。

正是XMLScriptBuilder和SqlNode這種靈活的設計才可以使xml標籤有非常強大的支持,同時解析的時候又不至於太複雜。

Java程式設計師日常學習筆記,如理解有誤歡迎各位交流討論!

相關焦點

  • Mybatis中mapper的xml解析詳解
    的流程,關鍵代碼如下:解析mapper有4種情況可以分成根據類解析和根據xml文件解析兩類,這次是後面這類,主體方法還是比較簡單。最後執行parse方法去解析xml文件內容。實際上XMLMapperBuilder與XMLConfigBuilder都繼承BaseBuilder類,通過名字也可以看出來他們採用的是構建者模式,在結合他們的使用流程就能夠更加理解mybatis採用構建者模式對不同的xml解析過程。
  • 從0 開始手寫一個 Mybatis 框架,三步搞定!
    1Mybatis框架流程簡介在手寫自己的Mybatis框架之前,我們先來了解一下Mybatis,它的源碼中使用了大量的設計模式,閱讀源碼並觀察設計模式在其中的應用,才能夠更深入的理解源碼(ref:Mybatis源碼解讀-設計模式總結)。
  • 比mybatis 強大優雅的 sqltoy-orm-4.11.6 發版了
    在目前有這麼多ORM框架的情況下,再搞一個開源框架的前提就是必須要比之前的好很多,而在中國如果不超過mybatis(plus)就根本沒有必要投入精力做這件事!因為大家知道開源就是在別人忙掙錢或者玩樂的時候而你卻在不計得失的奉獻!而我希望給大家奉獻一個真正有趣的有靈魂的框架!
  • 徹底搞懂MyBatis插件原理及PageHelper原理
    >插件的加載插件如何進行攔截攔截Executor對象其他對象插件解析插件執行流程假如一個對象被代理很多次插件的加載既然插件需要在配置文件中進行配置,那麼肯定就需要進行解析,我們看看插件式如何被解析的。其他對象插件解析接下來我們再看看StatementHandler,StatementHandler是在Executor中的doQuery方法創建的,其實這個原理就是一樣的了,找到初始化StatementHandler對象的方法:在這裡插入圖片描述進去之后里面執行的也是pluginAll方法:
  • MyBatis JPA Extra,MyBatis JPA 擴展 v2.2 發布
    >1、JavaBean注釋簡單只支持6個注釋 @Entity @Table @Column @Id @GeneratedValue @Transient@GeneratedValue有3中策略;import org.apache.mybatis.jpa.test.domain.Students;import org.apache.mybatis.jpa.util.WebContext;import
  • 重學Java 設計模式:實戰代理模式「模擬mybatis-spring中定義DAO...
    四、案例場景模擬場景模擬;實現mybatis-spring中代理類生成部分「在本案例中我們模擬實現mybatis-spring中代理類生成部分」對於Mybatis的使用中只需要定義接口不需要寫實現類就可以完成增刪改查操作,有疑問的小夥伴,在本章節中就可以學習到這部分知識。
  • mybatis中SqlSessionFactory類創建過程
    mybatis執行主要流程上一篇文章中介紹的mybatis源碼環境中的測試代碼如下圖:可以看到可mybatis相關的實際上就只有三步:創建SqlSessionFactory、通過SqlSessionFactory創建SqlSession、SqlSession
  • 面試官問你MyBatis SQL是如何執行的?把這篇文章甩給他
    數據處理層配置解析在 Mybatis 初始化過程中,會加載 mybatis-config.xml配置文件、映射配置文件以及 Mapper 接口中的註解信息,解析後的配置信息會形成相應的對象並保存到Configration
  • 極致性能 sqltoy-orm-4.12.10 發版 - OSCHINA - 中文開源技術交流...
    根本上杜絕了sql注入問題 最科學的sql編寫方式* sqltoy的sql編寫(支持嵌套)select *from sqltoy_device_order_info t where #[t.ORDER_ID=:orderId] #[and t.ORGAN_ID in (:authedOrganIds)] #[and t.STAFF_ID in (:staffIds)] #[and t.TRANS_DATE>=:beginDate] #[and t.TRANS_DATE<:endDate] * mybatis
  • SpringBoot + MyBatis + MySQL讀寫分離實踐!
    這裡我們選擇程序自己來做,主要是利用Spring提供的路由數據源,以及AOP然而,應用程式層面去做讀寫分離最大的弱點(不足之處)在於無法動態增加資料庫節點,因為數據源配置都是寫在配置中的,新增資料庫意味著新加一個數據源,必然改配置,並重啟應用。當然,好處就是相對簡單。2.
  • 極致查詢性能 sqltoy-orm-4.12.6 發版 - OSCHINA - 中文開源技術...
    /tree/master/trunk/sqltoy-nosql見test下面MongoTest.javasqltoy特點說明: 支持mysql、postgresql、db2、oracle、sqlserver、sqlite、clickhouse、elasticsearch、mongodb等
  • 看到Mybatis源碼就感到煩躁,怎麼辦?
    配置文件的解析就是在這裡完成的。包括mybatis-config.xml和我們的Mapper.xml映射器文件。這一步我們關心的內容是:解析的時候做了什麼?產生了什麼對象,解析的結果放在哪裡的。因為這將意味著,我們後面使用的時候去哪裡獲取這項配置項內容。
  • 優雅的 ORM 框架 sqltoy-orm-4.11.9 發版了
    因為針對常規的CRUD sqltoy跟大家並無較大差異!如果您的數據規模較大,涉及相對複雜的查詢已經影響到了用戶體驗,可以深入了解sqltoy,對你會有較大的幫助!,,給開發更多信息4、查詢返回結果支持List<Object[]>,同時支持resultType 直接給Map.class等接口(之前必須是HashMap等實現類)、5、quickvo支持yml格式的配置文件6、增強sql執行輸出#xml配置模式<bean id="sqlToyContext" name="sqlToyContext
  • 極致查詢性能 sqltoy-orm-4.12.3 發版 - OSCHINA - 中文開源技術...
    /tree/master/trunk/sqltoy-sharding分庫分表說明: https://chenrenfei.github.io/sqltoy/#/sqltoy/shardingsqltoy特點說明: 支持mysql、postgresql、db2、oracle、sqlserver、sqlite
  • 帶你快速了解spark sql
    sql執行spark任務的分布式解析引擎。它能夠將用戶編寫的sql語言解析成RDD對應的分布式任務,由於spark是基於內存去處理、計算數據集,所以其執行速度非常快。spark sql對應的結構可以總結為下圖所示:DataSet,顧名思義,就是數據集的意思,它是 Spark 1.6 新引入的接口。
  • SQL on file 工具
    csvsql既然是命令行工具,csvsql必然具備短小快捷的優點,比如帶列名的sales.csv文件,按client列分組,對每組的amount列求和,只需在命令行簡單寫一句:遺憾的是,csvsql除了體積小、編寫SQL快捷之外,就只剩缺點了,其中最大的缺點是安裝配置複雜。
  • 面試官:Mybatis 使用了哪些設計模式?
    在這個過程中,有一個相似的特點,就是這些Builder會讀取文件或者配置,然後做大量的XpathParser解析、配置或語法的解析、反射生成對象、存入結果緩存等步驟,這麼多的工作都不是一個構造函數所能包括的,因此大量採用了Builder模式來解決。
  • mysql性能分析explain之id詳解
    前言在mysql的編程世界裡,有時候我們往往需要對自己編寫的sql語句進行分析,來去查看sql的執行計劃,這個還是很有必要的。因為我們在開發中,往往需要從資料庫中查詢數據,當數據量一旦大的時候,這個時候就會出現查詢瓶頸,我們首先呢可能就會去查看我們的sql語句的執行過程,判斷是否可進行優化,那如何去定位分析呢?這個就是本文講到的使用explain工具來去定位分析。說明由於explain這個分析工具查詢出來的欄位過多,本文主要講第一部分,id的講解。
  • 大數據分析工程師入門9-Spark SQL
    >2)解析分區信息parquet文件中如果帶有分區信息,那麼SparkSQL會自動解析分區信息。:string(nullable=true)目前自動解析分區支持數值類型和字符串類型。自動解析分區類型的參數為:spark.sql.sources.partitionColumnTypeInference.enabled,默認值為true。可以關閉該功能,直接將該參數設置為disabled。此時,分區列數據格式將被默認設置為string類型,不會再進行類型解析。
  • SQL中EXPLAIN命令詳解
    SIMPLE: 簡單SELECT(不使用UNION或子查詢) PRIMARY: 最外面的SELECT UNION:UNION中的第二個或後面的SELECT語句 DEPENDENT UNION:UNION中的第二個或後面的SELECT語句,取決於外面的查詢UNION RESULT:UNION的結果 SUBQUERY:子查詢中的第一個SELECT