有關MyBatis動態SQL
MyBatis動態SQL是一種根據不同條件靈活拼接SQL語句的技術,基于OGNL表達式實現。動態 SQL 大大減少了編寫代碼的工作量,更體現了 MyBatis 的靈活性、高度可配置性和可維護性。
1.什么是動態SQL?
動態sql可以在一些需要靈活拼接sql語句的場景中使用,作用是根據一些條件,拼接出需要的sql語句。
動態 SQL 是 MyBatis 的強大特性之一。在 JDBC 或其它類似的框架中,開發人員通常需要手動拼接 SQL 語句。根據不同的條件拼接 SQL 語句是一件極其痛苦的工作。而動態 SQL 恰好解決了這一問題,可以根據場景動態的構建查詢。
動態 SQL 只有幾個基本元素,與 JSTL 或 XML 文本處理器相似,十分簡單明了,大量的判斷都可以在 MyBatis 的映射 XML 文件里配置,以達到許多需要大量代碼才能實現的功能。
MyBatis 也可以在注解中配置 SQL,但是由于注解功能受限,且對于復雜的 SQL 語句來說可讀性差,所以使用較少。本教程不對它們進行介紹。
2.動態SQL的作用與原理
2.1 作用
- 靈活拼接SQL:根據參數值動態添加或刪除SQL片段,例如條件查詢、批量操作、多分支選擇等場景
- 簡化代碼:避免手動處理逗號、空格、WHERE/AND/OR等語法細節,提升代碼可讀性
- 提高安全性:通過預編譯機制(
#{}
)防止SQL注入,但需注意復雜邏輯下的潛在風險
2.2 執行原理
-
OGNL表達式:MyBatis使用OGNL(Object-Graph Navigation Language)從參數對象中解析條件值,判斷是否包含特定SQL片段
-
動態解析流程:
① XML中的SQL標簽(如
<if>
<where>
、)被解析為SqlNode
對象;② 運行時根據參數值生成
BoundSql
對象,拼接最終SQL;③ 核心類如
OgnlExpressionEvaluator
處理表達式計算,TrimSqlNode
、WhereSqlNode
等處理語法修剪;
3.常用動態SQL標簽及用法
MyBatis 的動態 SQL 包括以下幾種元素,如下表所示。
元素 | 作用 | 備注 |
---|---|---|
if | 判斷語句 | 單條件分支判斷 |
choose(when、otherwise) | 相當于 Java 中的 switch case 語句 | 多條件分支判斷 |
trim、where | 輔助元素 | 用于處理一些SQL拼裝問題 |
foreach | 循環語句 | 在in語句等列舉條件常用 |
bind | 輔助元素 | 拼接參數 |
3.1 <if>
標簽
-
功能:通過
test
屬性判斷條件是否成立,決定是否包含SQL片段。當判斷條件為 true 時,才會執行所包含的 SQL 語句 -
示例:根據姓名和年齡動態查詢用戶:
<select id="selectUser">SELECT * FROM user<where><if test="name != null">AND name = #{name}</if><if test="age > 0">AND age = #{age}</if></where> </select>
- 示例:可多個 if 語句同時使用。以下語句表示為可以按照student名稱(stu_name)或者stu_age進行模糊查詢。如果您不輸入名稱或年齡,則返回所有的student記錄。但是,如果你傳遞了任意一個參數,它就會返回與給定參數相匹配的記錄。
<select id="select13" resultType="Student">select * from student where true<if test="name!=null">and stu_name='${name}'</if><if test="age!=null">and stu_age=${age}</if></select>
注意:字符串比較需用
toString()
避免類型錯誤,如test="sex == '1'.toString()"
3.2 <where>
標簽
-
功能:自動生成
WHERE
子句,并去除首個條件的AND
/OR
前綴,避免語法錯誤。 -
if 語句中判斷條件為 true 時,where 關鍵字才會加入到組裝的 SQL 里面,否則就不加入。where 會檢索語句,它會將 where 后的第一個 SQL 條件語句的 AND 或者 OR 關鍵詞去掉。
-
示例:
<where><if test="name != null">name = #{name}</if><if test="age != null">AND age = #{age}</if> </where>
- 最佳實踐:每個條件建議以
AND
/OR
開頭,<where>
會自動處理首前綴,where后面的意思是,如果后面if條件中有成立的語句,則加上where語句,如果沒有成立的條件語句,則不加where條件,第一個成立條件的語句,前面的and或者or自動刪除。
- 最佳實踐:每個條件建議以
3.3 <trim>
標簽
<trim prefix="前綴" suffix="后綴" prefixOverrides="忽略前綴字符" suffixOverrides="忽略后綴字符">SQL語句
</trim>
- 功能:自定義前綴/后綴的添加或刪除,適用于復雜場景。
- 屬性:
prefix
(添加前綴)、suffix
(添加后綴)、prefixOverrides
(刪除前綴)、suffixOverrides
(刪除后綴)。
trim 中屬性說明如下。
屬性 | 描述 |
---|---|
prefix | 給SQL語句拼接的前綴,為 trim 包含的內容加上前綴 |
suffix | 給SQL語句拼接的后綴,為 trim 包含的內容加上后綴 |
prefixOverrides | 去除 SQL 語句前面的關鍵字或字符,該關鍵字或者字符由 prefixOverrides 屬性指定。 |
suffixOverrides | 去除 SQL 語句后面的關鍵字或者字符,該關鍵字或者字符由 suffixOverrides 屬性指定。 |
-
示例:替代
<where>
實現相同功能:<trim prefix="WHERE" prefixOverrides="AND |OR "><if test="name != null">AND name = #{name}</if> </trim>
3.4 <choose>
、<when>
、<otherwise>
標簽
-
功能:實現多分支選擇邏輯,類似Java的
switch-case-default
-
<choose><when test="判斷條件1">SQL語句1</when><when test="判斷條件2">SQL語句2</when><when test="判斷條件3">SQL語句3</when><otherwise>SQL語句4</otherwise> </choose>
-
示例:
<choose><when test="name != null">AND name = #{name}</when><when test="age != null">AND age = #{age}</when><otherwise>AND status = 1</otherwise> </choose>
3.5 <foreach>
標簽
對于一些 SQL 語句中含有 in 條件,需要迭代條件集合來生成的情況,可以使用 foreach 來實現 SQL 條件的迭代。
<foreach item="item" index="index" collection="list|array|map key" open="(" separator="," close=")">參數值
</foreach>
參數說明:
foreach 標簽主要有以下屬性,說明如下。
- item:表示集合中每一個元素進行迭代時的別名。
- index:指定一個名字,表示在迭代過程中每次迭代到的位置。
- open:表示該語句以什么開始(既然是 in 條件語句,所以必然以(開始)。
- separator:表示在每次進行迭代之間以什么符號作為分隔符(既然是 in 條件語句,所以必然以,作為分隔符)。
- close:表示該語句以什么結束(既然是 in 條件語句,所以必然以)開始)。
使用 foreach 標簽時,最關鍵、最容易出錯的是 collection 屬性,該屬性是必選的,但在不同情況下該屬性的值是不一樣的,主要有以下 3 種情況:
- 如果傳入的是單參數且參數類型是一個 List,collection 屬性值為 list。
- 如果傳入的是單參數且參數類型是一個 array 數組,collection 的屬性值為 array。
- 如果傳入的參數是多個,需要把它們封裝成一個 Map,當然單參數也可以封裝成 Map。Map 的 key 是參數名,collection 屬性值是傳入的 List 或 array 對象在自己封裝的 Map 中的 key。
-
功能:遍歷集合(如List、Array),生成批量操作的SQL,常用于
IN
語句或批量插入。 -
示例:根據ID列表查詢用戶:
SELECT * FROM user WHERE id IN <foreach collection="idList" item="id" open="(" separator="," close=")">#{id} </foreach>
3.6 <set>
標簽
-
功能:在更新操作中動態生成
SET
子句,自動去除末尾逗號。 -
示例:
<update id="updateUser">UPDATE user<set><if test="name != null">name = #{name},</if><if test="age != null">age = #{age}</if></set>WHERE id = #{id} </update>
3.7 bind
標簽
每個數據庫的拼接函數或連接符號都不同,例如 MySQL 的 concat 函數、Oracle 的連接符號“||”等。這樣 SQL 映射文件就需要根據不同的數據庫提供不同的實現,顯然比較麻煩,且不利于代碼的移植。幸運的是,MyBatis 提供了 bind 標簽來解決這一問題。
bind 標簽可以通過 OGNL 表達式自定義一個上下文變量。
比如,按照名稱進行模糊查詢,SQL 映射文件如下。
<select id="select14e" resultType="Student"><bind name="myvalue" value="'%'+stu_name+'%'" />SELECT *FROM studentWHERE stu_name like #{myvalue}
</select>
bind 元素屬性如下:
- value:對應傳入實體類的某個字段,可以進行字符串拼接等特殊處理。
- name:給對應參數取的別名。
以上代碼中的“_parameter”代表傳遞進來的參數,它和通配符連接后,賦給了 pattern,然后就可以在 select 語句中使用這個變量進行模糊查詢,不管是 MySQL 數據庫還是 Oracle 數據庫都可以使用這樣的語句,提高了可移植性。
4.使用建議與注意事項
-
優先使用
<where>
和<set>
:避免手動添加1=1
或處理逗號,提高代碼簡潔性 -
模糊查詢優化:使用
<bind>
標簽統一處理模糊匹配,如<bind name="nameLike" value="'%' + name + '%'" />
,解決不同數據庫的語法差異 -
性能考量:動態SQL可能影響執行計劃優化,復雜條件建議結合索引設計使用