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

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

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