上一篇分析了SqlSession執行sql的過程,其中並沒有分析sql是從哪裡來的,今天就來仔細分析下。
Sql來源
從上一篇的最後一步執行sql那裡倒推sql的來源,源碼主要過程如下圖:
可以看到最後是通過BoundSql直接獲取的sql,然後往前倒推最後發現是通過MappedStatement的getBoundSql方法返回的。MappedStatement在之前分析mapper的時候知道一個執行sql對應一個MappedStatement對象,它封裝有mybatis中需要執行一條sql的所有信息,所以從這裡獲取也是理所應當的。
MappedStatement的getBoundSql方法
那麼就來看下MappedStatement的getBoundSql方法吧,源碼如下圖:
首先是右邊MappedStatement的getBoundSql方法,這次改了下sql傳遞了兩個參數,但是對程序基本沒什麼影響。可以看到BoundSql是通過SqlSource創建的,通過debug知道是DynamicSqlSource對象。
左邊是DynamicSqlSource的getBoundSql方法,可以先看後面的創建BoundSql對象,是通過SqlSource初始化的,而創建SqlSource對象需要的第一個參數是通過context.getSql()得到的。
而在getBoundSql方法第一步是初始化了context,從上圖的debug可以看到目前context的信息,bindings屬性已經通過初始化把參數設置進去,而sqlBuilder還是一個空字符串。關鍵代碼就在當前打斷點的「rootSqlNode.apply(context);」這行代碼。
從上圖可以看到rootSqlNode是一個MixedSqlNode對象,MixedSqlNode對象的apply方法是遍歷屬性contents的所有元素並執行它們的apply方法,可以看到contents只有一個TextSqlNode對象的元素。所以最終來到TextSqlNode對象。
TextSqlNode對象的text屬性保存的就是還沒有進行處理的sql。
TextSqlNode處理sql
TextSqlNode處理sql的源碼如下圖:
右側是TextSqlNode的apply方法,參數context就是上一步需要的context,他裡面包含有請求參數,從上一張源碼圖可以知道它的屬性sqlBuilder此時還是一個空字符串,而apply就是去拼接sqlBuilder。
apply方法首先創建了一個GenericTokenParser對象,GenericTokenParser對象有三個屬性openToken、closeToken、handler。其中openToken、closeToken分別對應字符串」${」、」}」,而handler的屬性context對應傳遞進來的context,injectionFilter屬性是TextSqlNode的injectionFilter。apply第二行代碼「context.appendSql(parser.parse(text));」可以看出來parser.parse(text)就是生成sql的代碼。其中text是TextSqlNode中沒有處理的原始sql。
所以sql生成在GenericTokenParser的parse方法,parse方法的關鍵源碼如上圖左側。主要步驟分析如下:
1、從sql找到第一個「${」位置start,獲取到元素sql(text)的字符數組src;
2、把src從開始到start處的字符拼接到結果builder上,也就是把「${」的內容拼接到結果上;
3、從start+2的位置開始找「}」的位置end,從src數組裡取出「${」、「}」之間的字符組成字符串並根據字符串從hander中取出字符串對應的參數值。並把值拼接到結果builder上。
最後還有一點代碼沒有截取到「start = text.indexOf(openToken, offset);
} while (start > -1);」也就是第4步。
4、繼續找到下一個「${」並拼接。最終組成完整的sql。
實際上GenericTokenParser的parse方法還是比較簡單的,就是把傳遞進來的參數text中openToken、closeToken中間的內容替換成對應的參數。
總結
從之前的分析我們知道一個MappedStatement對應一個sql,那麼如何從MappedStatement獲取組裝好的sql呢,實際上是MappedStatement的屬性sqlSource。
而sqlSource實際上是依靠rootSqlNode,在之前我們分析過解析mapper文件是把sql生成了嵌套的各種SqlNode子類。今天就看到他們的使用了。不過今天的算是比較簡單的,後面來一個稍微複雜一點的看看sql的解析過程。
Java程式設計師日常學習筆記,如理解有誤歡迎各位交流討論!