MyBatis 是一個(gè)優(yōu)秀的持久層框架,它提供了豐富的 SQL 映射功能,可以讓我們通過(guò) XML 或注解方式來(lái)定義 SQL 語(yǔ)句。它很大程度上簡(jiǎn)化了數(shù)據(jù)庫(kù)操作,提高了開(kāi)發(fā)效率。動(dòng)態(tài) SQL 是其中一個(gè)非常重要的功能,可以讓我們根據(jù)不同的條件動(dòng)態(tài)生成 SQL 語(yǔ)句,提高了 SQL 的靈活性和可重用性。本文將詳細(xì)介紹 MyBatis 的動(dòng)態(tài) SQL 使用與原理。
一、動(dòng)態(tài)SQL概述
動(dòng)態(tài)SQL是指根據(jù)條件拼接SQL語(yǔ)句的功能,可以在SQL語(yǔ)句中添加或者刪除某些條件和語(yǔ)句。在實(shí)際開(kāi)發(fā)中,我們經(jīng)常需要根據(jù)不同的條件拼接不同的SQL語(yǔ)句。如果只使用靜態(tài)SQL,會(huì)使得代碼冗余度高、可讀性差、維護(hù)成本高等問(wèn)題。而使用動(dòng)態(tài)SQL可以很好地解決這些問(wèn)題。
MyBatis中提供了很多種方式來(lái)實(shí)現(xiàn)動(dòng)態(tài)SQL,包括if、choose、when、otherwise、trim、where、set等。
二、if標(biāo)簽
if標(biāo)簽是MyBatis中最常用的動(dòng)態(tài)SQL標(biāo)簽之一。它通常用來(lái)判斷條件是否成立,從而確定是否加入SQL語(yǔ)句中。下面是一段示例代碼:
<select id="selectUsers" resultMap="UserResultMap">
SELECT * FROM Users
<where>
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
上述代碼中,通過(guò)if標(biāo)簽的test屬性來(lái)判斷條件是否成立。只有當(dāng)"name"和"age"都不為空時(shí),才會(huì)將其加入到SQL語(yǔ)句中。這樣就可以在不同的情況下生成不同的SQL語(yǔ)句。
三、choose、when和otherwise標(biāo)簽
choose、when和otherwise標(biāo)簽通常一起使用,它類(lèi)似于JAVA中的switch語(yǔ)句。下面是一段示例代碼:
<select id="selectUsers" resultMap="UserResultMap">
SELECT * FROM Users
<where>
<choose>
<when test="name != null">
AND name = #{name}
</when>
<when test="age != null">
AND age = #{age}
</when>
<otherwise>
AND id > 0
</otherwise>
</choose>
</where>
</select>
choose、when和otherwise標(biāo)簽中,如果test條件成立,就會(huì)將當(dāng)前標(biāo)簽中的SQL語(yǔ)句加入到最終的SQL語(yǔ)句中。只有一個(gè)可以成立,多個(gè)成立時(shí)按順序第一個(gè)生效。
四、trim標(biāo)簽
trim標(biāo)簽通常用來(lái)去掉特定字符或者關(guān)鍵字。下面是一段示例代碼:
<select id="selectUsers" resultMap="UserResultMap">
SELECT * FROM Users
<where>
<trim prefix="AND" prefixOverrides="OR">
<if test="name != null">
OR name = #{name}
</if>
<if test="age != null">
OR age = #{age}
</if>
</trim>
</where>
</select>
上述代碼中,prefix屬性表示在標(biāo)簽內(nèi)部SQL語(yǔ)句前添加的字符;prefixOverrides屬性表示從標(biāo)簽內(nèi)部SQL語(yǔ)句開(kāi)頭去除的字符串。
五、set標(biāo)簽和where標(biāo)簽
set標(biāo)簽通常用來(lái)更新參數(shù)對(duì)象中的非空屬性。where標(biāo)簽通常用來(lái)拼接SQL語(yǔ)句中的where條件。下面是一段示例代碼:
<update id="updateUser" parameterType="User">
UPDATE Users
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
</set>
<where>
id = #{id}
</where>
</update>
上述代碼中,set標(biāo)簽用來(lái)設(shè)置要更新的字段,通過(guò)if標(biāo)簽判斷哪些字段需要更新。where標(biāo)簽用來(lái)拼接SQL語(yǔ)句中的where條件,具體的條件可以根據(jù)實(shí)際情況進(jìn)行調(diào)整。
六、foreach
foreach 標(biāo)簽用于處理集合類(lèi)型的參數(shù),比如 List、Array 等,可以遍歷集合中的元素,將每個(gè)元素都轉(zhuǎn)化為 SQL 語(yǔ)句的一部分,用于生成動(dòng)態(tài) SQL 語(yǔ)句。下面是一個(gè)示例:
<select id="getUserByIdList" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
在上述 SQL 語(yǔ)句中,我們通過(guò) foreach 標(biāo)簽遍歷傳入的參數(shù) idList,將其中的每個(gè)元素轉(zhuǎn)化為一個(gè) id,然后根據(jù)這些 id 拼接成一個(gè) IN 子句。
七、bind
bind 標(biāo)簽用于定義一個(gè)變量,該變量可以被后續(xù)的 SQL 片段引用,方便了 SQL 的編寫(xiě)。下面是一個(gè)示例:
<select id="getUserByName" resultType="User">
<bind name="queryName" value="'%' + name + '%'"/>
SELECT * FROM user WHERE name like #{queryName}
</select>
在上述 SQL 語(yǔ)句中,我們使用 bind 標(biāo)簽定義了一個(gè)變量 queryName,它的值為 name 模糊查詢(xún)的條件。然后使用該變量來(lái)拼接 SQL 語(yǔ)句,使得 SQL 語(yǔ)句更加簡(jiǎn)潔。
八、動(dòng)態(tài)SQL解析原理
MyBatis的動(dòng)態(tài)SQL是通過(guò)OGNL表達(dá)式來(lái)實(shí)現(xiàn)的。OGNL(Object-Graph Navigation Language)是一種基于Java對(duì)象圖遍歷的表達(dá)式語(yǔ)言,它可以方便地訪(fǎng)問(wèn)Java對(duì)象的屬性和方法。
在MyBatis中,通過(guò)OGNL表達(dá)式可以動(dòng)態(tài)地計(jì)算條件是否成立,從而確定是否將SQL片段添加到最終的SQL語(yǔ)句中。OGNL表達(dá)式通常嵌入在MyBatis中的動(dòng)態(tài)SQL標(biāo)簽中,例如if、choose、when、otherwise等。
MyBatis使用了兩個(gè)重要的類(lèi)來(lái)實(shí)現(xiàn)OGNL表達(dá)式的解析和計(jì)算:OgnlExpressionEvaluator和OgnlCache。OgnlExpressionEvaluator類(lèi)負(fù)責(zé)將MyBatis傳入的參數(shù)對(duì)象轉(zhuǎn)換為OGNL表達(dá)式需要的上下文對(duì)象,然后將OGNL表達(dá)式計(jì)算結(jié)果返回;OgnlCache類(lèi)負(fù)責(zé)緩存已經(jīng)解析好的OGNL表達(dá)式,避免重復(fù)解析和計(jì)算。
具體的解析過(guò)程如下:
- 根據(jù)MyBatis的配置將MApper.xml文件中的SQL語(yǔ)句解析為一個(gè)MappedStatement對(duì)象,并將其中的OGNL表達(dá)式解析成一個(gè)一個(gè)可執(zhí)行的SQL片段。
- 對(duì)于每一個(gè)OGNL表達(dá)式,MyBatis使用${}來(lái)表示一個(gè)簡(jiǎn)單的OGNL表達(dá)式,使用#{}來(lái)表示一個(gè)OGNL表達(dá)式中包含復(fù)雜邏輯的情況。在解析過(guò)程中,MyBatis會(huì)將OGNL表達(dá)式中的參數(shù)進(jìn)行解析和預(yù)處理,然后使用OgnlCache類(lèi)將其緩存起來(lái)。
- 當(dāng)Mapper接口方法被調(diào)用時(shí),MyBatis會(huì)將方法中傳入的參數(shù)對(duì)象轉(zhuǎn)換為一個(gè)BoundSql對(duì)象,并將該BoundSql對(duì)象與MappedStatement對(duì)象一起傳遞給OgnlExpressionEvaluator類(lèi)。
- OgnlExpressionEvaluator類(lèi)中再次解析OGNL表達(dá)式,并將BoundSql對(duì)象作為上下文傳入OGNL表達(dá)式中執(zhí)行。OGNL表達(dá)式執(zhí)行的結(jié)果將被轉(zhuǎn)化為String類(lèi)型,并返回給BoundSql對(duì)象。
- 最后,MyBatis將所有BoundSql對(duì)象中的SQL片段拼接成最終的SQL語(yǔ)句并執(zhí)行。
MyBatis的動(dòng)態(tài)SQL解析原理是將OGNL表達(dá)式解析為可執(zhí)行的SQL片段,然后根據(jù)條件判斷是否將該SQL片段加入到最終的SQL語(yǔ)句中。MyBatis使用OgnlExpressionEvaluator和OgnlCache類(lèi)來(lái)實(shí)現(xiàn)OGNL表達(dá)式的解析和計(jì)算,從而實(shí)現(xiàn)動(dòng)態(tài)SQL的功能。
在 MyBatis 的源碼中,動(dòng)態(tài) SQL 還涉及到以下接口和類(lèi)來(lái)實(shí)現(xiàn):
- SqlNode 接口:表示一個(gè) SQL 節(jié)點(diǎn),也就是一個(gè) SQL 片段。它包含一個(gè) apply 方法,在執(zhí)行 SQL 語(yǔ)句時(shí)會(huì)將 SQL 片段應(yīng)用到相應(yīng)的位置。
- MixedSqlNode 類(lèi):實(shí)現(xiàn)了 SqlNode 接口,可以包含多個(gè)子節(jié)點(diǎn)。該類(lèi)的 apply 方法會(huì)依次遍歷所有子節(jié)點(diǎn),并將每個(gè)節(jié)點(diǎn)應(yīng)用到 SQL 語(yǔ)句中。
- TextSqlNode 類(lèi):表示一個(gè)純文本節(jié)點(diǎn)。該類(lèi)包含一個(gè)文本字符串,可以將其直接應(yīng)用到 SQL 語(yǔ)句中。
- IfSqlNode 類(lèi):表示一個(gè)條件節(jié)點(diǎn)。可以根據(jù)指定的條件判斷是否需要應(yīng)用該節(jié)點(diǎn)內(nèi)部的 SQL 片段。如果條件成立,則會(huì)將 SQL 片段應(yīng)用到 SQL 語(yǔ)句中。
- TrimSqlNode 類(lèi):表示一個(gè)修剪節(jié)點(diǎn),可以根據(jù)配置對(duì) SQL 片段進(jìn)行修剪操作。常用于處理 UPDATE 和 INSERT 語(yǔ)句中 SET 子句的逗號(hào)問(wèn)題。
- WhereSqlNode 類(lèi):表示一個(gè) WHERE 條件節(jié)點(diǎn)。可以將 WHERE 子句的參數(shù)拼接到 SQL 語(yǔ)句中。

以上是 MyBatis 中實(shí)現(xiàn)動(dòng)態(tài) SQL 的核心接口和類(lèi)。MyBatis 內(nèi)部通過(guò)組合這些接口和類(lèi)來(lái)構(gòu)建復(fù)雜的 SQL 語(yǔ)句。通過(guò)定義這些接口和類(lèi),可以讓開(kāi)發(fā)者更加方便地書(shū)寫(xiě)動(dòng)態(tài) SQL 語(yǔ)句,并且遵循了設(shè)計(jì)模式中的單一職責(zé)原則。
還有一些Builder 接口及其實(shí)現(xiàn)類(lèi)的作用都是用于構(gòu)造 SQL 語(yǔ)句。下面簡(jiǎn)單介紹一下一些常用的 Builder 類(lèi)型:
- BaseBuilder 接口:所有 Builder 的基礎(chǔ)接口,定義了一些共同的方法,例如獲取 Configuration 對(duì)象、創(chuàng)建 ParameterMapping 對(duì)象等。
- XMLMapperBuilder 類(lèi):從 XML 文件中解析出各種 SQL 節(jié)點(diǎn),然后通過(guò)其他 Builder 對(duì)象將其轉(zhuǎn)換成 SQL 語(yǔ)句。
- MapperBuilderAssistant 類(lèi):輔助 XMLMapperBuilder 類(lèi)創(chuàng)建各種類(lèi)型的 SQL 節(jié)點(diǎn),例如創(chuàng)建 <select>、<update>、<insert> 等標(biāo)簽節(jié)點(diǎn)。
- SqlSourceBuilder 類(lèi):根據(jù) XML 中的 SQL 片段創(chuàng)建 SqlSource 對(duì)象,SqlSource 對(duì)象中包含了解析后的 SQL 語(yǔ)句和參數(shù)信息。
- DynamicSqlSource 類(lèi):用于處理動(dòng)態(tài) SQL,也就是包含各種條件判斷和循環(huán)語(yǔ)句的 SQL 片段。它是 SqlSource 接口的一種實(shí)現(xiàn)。
- StaticSqlSource 類(lèi):用于處理靜態(tài) SQL,即不包含任何條件語(yǔ)句和循環(huán)語(yǔ)句的 SQL 片段。它同樣是 SqlSource 接口的一種實(shí)現(xiàn)。
- SqlSessionFactoryBuilder 類(lèi):用于創(chuàng)建 SqlSessionFactory 對(duì)象,它會(huì)將所有的 Builder 對(duì)象組合在一起,完成 SQL 語(yǔ)句的解析和構(gòu)造。

通過(guò)上述不同類(lèi)型的 Builder 對(duì)象,我們可以將 XML 中的 SQL 片段轉(zhuǎn)換成 Java 對(duì)象,并且根據(jù)各種條件生成相應(yīng)的 SQL 語(yǔ)句。這個(gè)過(guò)程中涉及到的類(lèi)和方法非常多,需要我們深入地了解 MyBatis 的內(nèi)部實(shí)現(xiàn)才能靈活運(yùn)用。
九、總結(jié)
本文通過(guò)介紹MyBatis動(dòng)態(tài)SQL的基本概念和常用標(biāo)簽(if、choose、when、otherwise、trim、where、set、foreach),希望讀者能夠更加深入地了解MyBatis的使用和原理。在實(shí)際開(kāi)發(fā)過(guò)程中,要根據(jù)具體場(chǎng)景和需求選擇合適的動(dòng)態(tài)SQL標(biāo)簽,從而實(shí)現(xiàn)靈活拼接SQL語(yǔ)句的功能,提高開(kāi)發(fā)效率。
在 MyBatis 中,動(dòng)態(tài) SQL 主要包括以下幾種類(lèi)型:
- <if> 標(biāo)簽:表示一個(gè)條件語(yǔ)句,可以根據(jù)條件判斷是否包含相應(yīng)的 SQL 片段。
- <where> 標(biāo)簽:表示一個(gè) WHERE 條件語(yǔ)句,可以根據(jù)配置自動(dòng)添加 WHERE 關(guān)鍵字。
- <choose> 標(biāo)簽:表示一個(gè)選擇語(yǔ)句,可以根據(jù)多個(gè)條件選擇符合條件的 SQL 片段。
- <foreach> 標(biāo)簽:表示一個(gè)循環(huán)語(yǔ)句,在循環(huán)中動(dòng)態(tài)生成 SQL 語(yǔ)句。
- <set> 標(biāo)簽:表示一個(gè) SET 子句,可以根據(jù)指定的屬性值動(dòng)態(tài)生成 SET 語(yǔ)句。
以上標(biāo)簽都屬于動(dòng)態(tài) SQL,在解析時(shí)需要通過(guò)特殊的方式進(jìn)行處理。下面以 <if> 標(biāo)簽為例介紹解析原理:
- XMLScriptBuilder 類(lèi)會(huì)根據(jù)標(biāo)簽類(lèi)型創(chuàng)建相應(yīng)的 SQL 節(jié)點(diǎn),例如 <if> 標(biāo)簽對(duì)應(yīng)的節(jié)點(diǎn)是 IfSqlNode 對(duì)象。
- XMLScriptBuilder 類(lèi)會(huì)遞歸解析節(jié)點(diǎn)內(nèi)部的子節(jié)點(diǎn),并將其組合成一個(gè) SQL 片段。
- 當(dāng)解析到 IfSqlNode 節(jié)點(diǎn)時(shí),XMLScriptBuilder 類(lèi)會(huì)獲取標(biāo)簽中的 test 屬性,并根據(jù)該屬性值創(chuàng)建一個(gè) OgnlExpression 對(duì)象(OGNL 表達(dá)式對(duì)象),用于判斷條件是否滿(mǎn)足。
- 如果條件滿(mǎn)足,則將子節(jié)點(diǎn)生成的 SQL 片段添加到當(dāng)前 SQL 上下文中;否則忽略該節(jié)點(diǎn)。
- 最終生成的 SQL 語(yǔ)句就是將所有滿(mǎn)足條件的 SQL 片段組合起來(lái)得到的。
以上就是 MyBatis 實(shí)現(xiàn)動(dòng)態(tài) SQL 解析的大體流程。通過(guò) XMLScriptBuilder 類(lèi)的遞歸解析,可以將各種類(lèi)型的動(dòng)態(tài) SQL 節(jié)點(diǎn)轉(zhuǎn)換成 SqlNode 接口的實(shí)現(xiàn),然后通過(guò) MixedSqlNode 類(lèi)將它們組合成一個(gè)完整的 SQL 片段。
作者:Cosolar
鏈接:
https://juejin.cn/post/7231921877466677285
來(lái)源:稀土掘金