系列文章目錄
MyBatis緩存原理
Mybatis的CachingExecutor與二級緩存
Mybatis plugin 的使用及原理
MyBatis四大組件Executor、StatementHandler、ParameterHandler、ResultSetHandler 詳解
MyBatis+Springboot 啟動到SQL執行全流程
數據庫操作不再困難,MyBatis動態Sql標簽解析
- 系列文章目錄
- 一、動態sql是什么?
- 二、動態sql原理
- 三、動態標簽解釋
- 1. if 標簽
- 2 choose、when 和 otherwise 標簽
- 3 trim 標簽
- 4 foreach 標簽
- 遍歷集合
- 遍歷數組
- 遍歷Map
- 5 set 標簽
- 6 bind 標簽
- 7 sql 與 include 標簽
- 8. where 標簽
- 總結
使用MyBatis,或者MyBatis-plus,有一項重要的開發技能就是寫動態sql,動態sql能幫我們省略很多復雜邏輯,也能幫我們解決一些格式問題,所以今天我們就來幫大家掌握這個動態sql
📕作者簡介:戰斧,從事金融IT行業,有著多年一線開發、架構經驗;愛好廣泛,樂于分享,致力于創作更多高質量內容
📗本文收錄于 MyBatis專欄 ,有需要者,可直接訂閱專欄實時獲取更新
📘高質量專欄 云原生、RabbitMQ、Spring全家桶 等仍在更新,歡迎指導
📙Zookeeper Redis kafka docker netty等諸多框架,以及架構與分布式專題即將上線,敬請期待
一、動態sql是什么?
假設有一個電商網站,需要根據用戶提供的不同條件查詢商品信息,比如說價格、商品名、類別等等,我們需要寫出每個查詢信息對應的SQL語句,例如
SELECT * FROM Product WHERE price <= #{price};
SELECT * FROM Product WHERE productname = #{name}
SELECT * FROM Product WHERE type = #{type};
…
如果這樣各種各樣的篩選邏輯很多,我們就需要寫很多條類似的SQL語句,而且還需要判斷入參是否合理,是否為空等等。這樣我們的sql就會非常的難以維護。此時,我們就需要利用到MyBatis的動態SQL。
MyBatis的動態SQL是指在SQL語句中可以根據不同的條件動態生成不同的SQL語句,以適應不同的需求。動態SQL可以根據條件生成不同的SQL語句,比如WHERE條件、ORDER BY、動態判斷等等,從而實現更靈活的SQL編寫。動態SQL的主要作用是讓SQL語句更加靈活、可重用和易維護,提高應用程序的靈活性和可擴展性
二、動態sql原理
MyBatis動態sql標簽的實現原理主要是通過OGNL
表達式和Java反射機制來實現的。
首先,MyBatis會將動態sql標簽中的OGNL表達式解析成Java代碼。解析過程中,MyBatis會根據OGNL表達式的類型來生成相應的Java代碼片段。例如,當解析if標簽時,如果判斷條件中的OGNL表達式為布爾類型,則會生成一個if語句的Java代碼片段。
其次,MyBatis會使用Java反射機制獲取實體類的屬性值,并將屬性值傳遞給動態sql標簽中的OGNL表達式進行判斷。如果判斷條件滿足,則會將動態sql標簽中的內容添加到SQL語句中;反之,則會忽略動態sql標簽中的內容。
總體來講,MyBatis動態sql標簽的實現原理就是將OGNL表達式解析成Java代碼,并使用Java反射機制獲取實體類屬性值進行判斷,以實現SQL語句的動態生成。myBtais整體運行流程可以看往期的 MyBatis+Springboot 啟動到SQL執行全流程
三、動態標簽解釋
1. if 標簽
<if>
標簽允許你根據條件判斷是否包含特定的SQL片段,用于構建靈活的查詢語句,該標簽的使用方法如下:
- 在需要動態生成條件的SQL語句中使用<if>標簽。
- 在
<if>
標簽中添加一個test屬性,用于指定一個boolean表達式,這個表達式會決定是否將當前條件語句包含在生成的SQL語句中。 - 在
<if>
至</if>
標簽中設置需要動態生成的條件語句。
<select id="getUser" resultMap="userResultMap">SELECT * FROM userwhere 1 = 1<if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if>
</select>
在這個示例中,<if>
標簽用于根據條件動態生成SQL語句中的條件語句。如果傳遞到這個方法的參數中包含一個非空的name屬性,那么就會在SQL語句中添加一個“name = #{name}”的條件語句;如果包含一個非空的age屬性,那么就會在SQL語句中添加一個“age = #{age}”的條件語句。
需要注意的是,使用標簽時,必須要用“AND”或“OR”將多個<if>
標簽組合在一起,否則會出現語法錯誤。此外,還需要注意在使用標簽時,SQL語句的正確性和性能的折中。太多的動態條件可能會導致SQL語句生成的復雜度增加,從而影響查詢的性能
2 choose、when 和 otherwise 標簽
<choose>
、<when>
和<otherwise>
標簽通常一起使用來實現條件分支查詢。這種方式類似于Java中的switch語句或者if-else語句
- 其中,
<choose>
標簽類似于Java中的switch語句,表示一個選擇分支,包含多個<when>
和<otherwise>
標簽。 <when>
標簽類似于Java中的case語句,用于指定分支條件。當條件滿足時,執行<when>
標簽內的SQL語句。<otherwise>
標簽類似于Java中的default語句,用于指定默認條件分支。當其他分支條件都不滿足時,執行<otherwise>
標簽內的SQL語句。
<select id="getUsersByAgeAndName" parameterType="map" resultMap="userMap">SELECT * FROM users<where><choose><when test="age != null and name != null">age = #{age} AND name = #{name}</when><when test="age != null">age = #{age}</when><when test="name != null">name = #{name}</when><otherwise>1=1</otherwise></choose></where>
</select>
如上,我們使用<choose>
、<when>
和<otherwise>
標簽來實現條件分支查詢。這個查詢可以根據參數中的年齡和名字來查找用戶記錄,也可以只根據年齡或者名字來查找,或者不加任何條件查詢所有的用戶記錄
3 trim 標簽
<trim>
標簽用于修剪生成的SQL語句,可以用于移除不必要的關鍵字,特別是在拼接多個條件時非常有用,其主要是用于確定子句中的前綴與后綴。
<trim>
標簽具有以下屬性:
prefixOverrides:要刪除的前綴字符串,可以以“|”分割,添加多個詞
suffixOverrides:要刪除的后綴字符串,可以以“|”分割,添加多個詞
prefix:要添加到 SQL 語句的前綴字符串
suffix:要添加到 SQL 語句的后綴字符串
<select id="findUserByName" parameterType="java.lang.String" resultType="User">SELECT * FROM user<trim prefix="WHERE" prefixOverrides="OR | AND "><if test="name != null and name != ''">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></trim>
</select>
在這個示例中, <trim>
標簽用于動態構建 WHERE 子句。如果它會刪除前綴中的 OR 或 AND 關鍵字,并將 WHERE 關鍵字添加到 SQL 語句中。
當然,你也許會想,如果我把 prefixOverrides 和 prefix 是設置成一個詞,那這個詞還會不會插入到子句句首呢?答案是會的,因為這里的邏輯是先刪除prefixOverrides指定字符串,再添加prefix指定字符串
4 foreach 標簽
標簽用于循環處理集合類型的參數,生成多次重復的SQL片段,該標簽可以用于循環遍歷一個集合或數組,并在SQL語句中使用它的值。以下是標簽的使用方法:。
遍歷集合
<select id="findUserById" parameterType="java.lang.Integer">SELECT * FROM table WHERE id IN<foreach collection="list" item="item" open="(" separator="," close=")">#{item}</foreach>
</select>
其中,collection屬性指定要遍歷的集合,item屬性指定集合中每個元素的別名,open屬性指定在開始時添加的字符串,separator屬性指定在每個元素之間添加的字符串,close屬性指定在結束時添加的字符串。
例如,如果list是一個包含1、2、3的List對象,這個標簽會生成以下SQL語句:
SELECT * FROM table WHERE id IN (1,2,3)
遍歷數組
<foreach collection="array" item="item" open="(" separator="," close=")">#{item}
</foreach>
這個標簽與遍歷集合的標簽非常相似,只不過把collection屬性改為了數組對象
遍歷Map
假設我們有這樣一個map
Map<String, Object> searchCriteria = new HashMap<>();
searchCriteria.put("username", "john");
searchCriteria.put("age", 25);
searchCriteria.put("city", "New York");
然后我們配上這樣的xml
<select id="searchUsers" parameterType="map" resultType="User">SELECT * FROM user<foreach collection="searchCriteria" item="value" index="key" separator="AND" open="WHERE">${key} = #{value}</foreach>
</select>
其中,collection屬性指定要遍歷的Map集合,index屬性指定鍵的別名,item屬性指定值的別名,open屬性指定在開始時添加的字符串,separator屬性指定在每個元素之間添加的字符串。
最后我們能得到這樣的sql
SELECT * FROM user WHERE username = "john" AND age = 25 AND city = "New York"
5 set 標簽
標簽通常用于動態生成UPDATE語句的SET部分,可自動生成逗號分隔的鍵值對,<set>
標簽通常包含多個標簽或者標簽,用于動態生成要更新的列和值。
UPDATE user<set><if test="username != null">username = #{username},</if><if test="password != null">password = #{password},</if><if test="email != null">email = #{email},</if></set>WHERE id = #{id}
</update>
在這個示例中,標簽根據傳入的參數動態生成SET部分。set 元素會動態地在行首插入 SET 關鍵字,并會刪掉額外的逗號
6 bind 標簽
<bind>
用于將一個表達式綁定到一個變量上。綁定的變量可以在后面的SQL語句中引用。通常,標簽被用于簡化SQL語句中的重復表達式,提高SQL語句的可讀性和可維護性。
<select id="findUsers" parameterType="String" resultMap="userResultMap"><bind name="pattern" value="'%' + name + '%'"/>SELECT * FROM user WHERE name LIKE #{pattern}
</select>
在上面的例子中,標簽用于將一個字符串表達式綁定到名為pattern的變量上。變量的值是一個由‘%’,‘name’和‘%’組成的字符串,其中‘name’是一個參數。在后面的SQL語句中,#{pattern}即可引用綁定的變量。
在標簽中,name屬性指定變量的名稱,value屬性指定變量的值。可以在value屬性中使用表達式,變量名和參數Map中的鍵。
除了綁定表達式到變量上,標簽還可以用于綁定參數值到變量上。例如:
<select id="findUsersByAge" parameterType="int" resultMap="userResultMap"><bind name="minAge" value="age - 5"/><bind name="maxAge" value="age + 5"/>SELECT * FROM user WHERE age BETWEEN #{minAge} AND #{maxAge}
</select>
在上面的例子中,<bind>
標簽用于將一個整數參數值綁定到名為minAge和maxAge的變量上。變量的值是通過計算參數值得到的。在后面的SQL語句中,#{minAge}和#{maxAge}即可引用綁定的變量。
總之,<bind>
標簽是一個非常強大的MyBatis功能,可以提高SQL語句的可讀性和可維護性。在開發MyBatis應用程序時,應該靈活使用標簽,以便更好地管理SQL語句。
7 sql 與 include 標簽
<sql>
標簽用于定義可重用的SQL片段。在一個或多個SQL語句中,可以使用標簽來引用這些SQL片段。
<sql id="userColumns">id, username, age
</sql><select id="getAllUsers" resultType="User">SELECT <include refid="userColumns" />FROM user
</select><select id="getUsersByName" parameterType="String" resultType="User">SELECT <include refid="userColumns" />FROM userWHERENAME = #{name}
</select>
上述例子中,<include>
標簽引用了一個名為"userColumns"的SQL片段。引用的方式是通過refid屬性指定片段的ID。所有引用的SQL片段都會被添加到原始SQL語句中。這樣,其他sql,如果也是使用這幾個字段,則都可以引用
8. where 標簽
<where>
標簽的作用是用來包裹一個或多個 SQL 條件語句,如果這些條件都滿足,則會被加入到 SELECT 或 UPDATE 語句中,否則不會。它的使用方法如下:
1.將多個條件語句用 標簽包裹起來。
<select id="selectByCondition" parameterType="java.util.Map" resultMap="userMap">select * from user<where><if test="name != null and name!=''">and name like CONCAT('%',#{name},'%')</if><if test="age != null">and age = #{age}</if></where>
</select>
2.在 SQL 語句中使用 <where>
標簽時,會自動移除掉多余的 AND 或 OR,而在沒有任何條件語句時,<where>
標簽也會自動刪除自身,以避免 SQL 語法錯誤。例如,上述代碼在沒有任何條件時,生成的 SQL 語句為:
select * from user
而當輸入 age=20 時,生成的 SQL 語句為:
select * from userwhere age = 20
可以看出<where>
標簽是 MyBatis 中一個非常有用的標簽,它可以幫助我們更加方便地動態構建 SQL 語句,提高代碼的可讀性和可維護性。
總結
MyBatis的動態SQL功能極大地簡化了數據庫操作,使得針對不同查詢需求的SQL語句生成變得靈活而高效。通過深入了解和掌握<if>、<choose>、<when>、<otherwise>、<trim>、<foreach>、<set>、<bind>和<sql>等核心標簽,開發人員將大幅度減少因為參數不同,而寫不同sql的麻煩,此時的sql會自己按預設邏輯變成不同的樣式。因此,需要牢牢掌握。