Mybatis系列第10篇:動態SQL,這麼多種你都會?

来源:https://www.cnblogs.com/konglxblog/archive/2022/04/23/16181349.html
-Advertisement-
Play Games

Mybatis系列目標:從入門開始開始掌握一個高級開發所需要的Mybatis技能。 這是mybatis系列第10篇,源碼位於文章尾部! mybatis中一個比較強大的功能就是動態sql,記得在剛開始工作那會,當時使用jdbc開發系統,在java代碼中搞了很多判斷去拼接sql,代碼看起來比較亂,也不方 ...


Mybatis系列目標:從入門開始開始掌握一個高級開發所需要的Mybatis技能。

這是mybatis系列第10篇,源碼位於文章尾部!

mybatis中一個比較強大的功能就是動態sql,記得在剛開始工作那會,當時使用jdbc開發系統,在java代碼中搞了很多判斷去拼接sql,代碼看起來比較亂,也不方便維護和擴展。

mybatis在處理sql的拼接這塊簡直是我們的福音,基本上sql拼接的所有的痛點,mybatis都幫我們解決了。下麵我們來學一下mybatis中各種動態sql的用法。

案例sql腳本

  1. DROP DATABASE IF EXISTS `javacode2018`;
  2. CREATE DATABASE `javacode2018`;
  3. USE `javacode2018`;
  4. DROP TABLE IF EXISTS t_user;
  5. CREATE TABLE t_user(
  6.   id int AUTO_INCREMENT PRIMARY KEY COMMENT '用戶id',
  7.   name VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用戶名',
  8.   age SMALLINT NOT NULL DEFAULT 1 COMMENT '年齡'
  9. ) COMMENT '用戶表';
  10. INSERT INTO t_user VALUES (1,'路人甲Java',30),(2,'張學友',50),(3,'劉德華',50);

if元素

相當於java中的if判斷,語法:

  1. <if test="判斷條件">
  2. 需要追加的sql
  3. </if>

test的值為一個判斷表達式,寫法上採用OGNL表達式的方式,OGNL在struts2中用的比較多,本文暫時對ognl不做詳細介紹,有興趣的可以去查一下相關資料。

當test成立的時候,if體內部的sql會被拼接上。

如:

  1. <select id="getList1" resultType="com.javacode2018.chat05.demo8.model.UserModel" parameterType="map">
  2.     SELECT id,name,age FROM t_user
  3.     WHERE 1 = 1
  4.     <if test="id!=null">
  5.         AND id = #{id}
  6.     </if>
  7.     <if test="name!=null and name.toString()!=''">
  8.         AND name = #{name}
  9.     </if>
  10.     <if test="age!=null">
  11.         AND age = #{age}
  12.     </if>
  13. </select>

上面查詢用戶列表,參數為一個map,當map中id不為空的時候,將其作為條件查詢,如果name不為空,將name也作為條件,如果age不為空,將age也作為條件進行查詢

當只傳入id的時候,sql如下:

SELECT id,name,age FROM t_user WHERE 1 = 1 AND id = ? 

當3個參數都傳了,sql如下:

SELECT id,name,age FROM t_user WHERE 1 = 1 AND id = ? AND name = ? AND age = ?

上面這種寫法相對於java代碼看起來是不是清爽了很多,也更方便維護,大家註意一下sql中有個WHERE 1=1,如果沒有這個,上面單通過if元素就不好實現了,mybatis也有解決方案,稍後會說明。

choose/when/otherwise元素

這個相當於java中的if..else if..else,語法:

  1. <choose>
  2.     <when test="條件1">
  3.         滿足條件1追加的sql
  4.     </when>
  5.     <when test="條件2">
  6.         滿足條件2追加的sql
  7.     </when>
  8.     <when test="條件n">
  9.         滿足條件n追加的sql
  10.     </when>
  11.     <otherwise>
  12.         都不滿足追加的sql
  13.     </otherwise>
  14. </choose>

choose內部的條件滿足一個,choose內部的sql拼接就會結束。

otherwise屬於可選的,當所有條件都不滿足的時候,otherwise將起效。

如:

傳入id、name、age作為條件,按順序進行判斷,如果id不為空,將id作為條件,忽略其他條件,如果id為空,會判斷name是否為空,name不為空將name作為條件,如果name為空,再看看age是否為空,如果age不為空,將age作為條件。

  1. <select id="getList2" resultType="com.javacode2018.chat05.demo8.model.UserModel" parameterType="map">
  2.     SELECT id,name,age FROM t_user
  3.     WHERE 1 = 1
  4.     <choose>
  5.         <when test="id!=null">
  6.             AND id = #{id}
  7.         </when>
  8.         <when test="name!=null and name.toString()!=''">
  9.             AND name = #{name}
  10.         </when>
  11.         <when test="age!=null">
  12.             AND age = #{age}
  13.         </when>
  14.     </choose>
  15. </select>

如果id、name、age都傳了,sql如下:

SELECT id,name,age FROM t_user WHERE 1 = 1 AND id = ? 

如果值傳遞了name、age,sql如下:

SELECT id,name,age FROM t_user WHERE 1 = 1 AND name = ? 

name判斷在age前面,所以name條件先匹配上了。

where元素

上面2個案例的sql中都有where 1=1這部分代碼,雖然可以解決問題,但是看起來不美觀,如果將where 1=11=1這部分幹掉,上面的兩個案例都會出問題,where後面會多一個AND符號,mybatis中已經考慮到這種問題了,屬於通用性的問題,mybatis中通過where 元素來解決,當使用where元素的時候,mybatis會將where內部拼接的sql進行處理,會將這部分sql前面的AND 或者 OR給去掉,併在前面追加一個where,我們使用where元素來對上面的案例1進行改造,如下:

  1. <select id="getList1" resultType="com.javacode2018.chat05.demo8.model.UserModel" parameterType="map">
  2.     SELECT id,name,age FROM t_user
  3.     <where>
  4.         <if test="id!=null">
  5.             AND id = #{id}
  6.         </if>
  7.         <if test="name!=null and name.toString()!=''">
  8.             AND name = #{name}
  9.         </if>
  10.         <if test="age!=null">
  11.             AND age = #{age}
  12.         </if>
  13.     </where>
  14. </select>

where 1=1被替換成了where 元素

當傳入id、name的時候,where內部的sql會變成這樣:

AND id = ? AND name = ?

mybatis會對上面這個sql進行處理,將前面的AND給去掉,併在前面追加一個where,變成了下麵這樣

where id = ? AND name = ?

案例2也用where改造一下,變成了下麵這樣:

  1. <select id="getList2" resultType="com.javacode2018.chat05.demo8.model.UserModel" parameterType="map">
  2.     SELECT id,name,age FROM t_user
  3.     <where>
  4.         <choose>
  5.             <when test="id!=null">
  6.                 AND id = #{id}
  7.             </when>
  8.             <when test="name!=null and name.toString()!=''">
  9.                 AND name = #{name}
  10.             </when>
  11.             <when test="age!=null">
  12.                 AND age = #{age}
  13.             </when>
  14.         </choose>
  15.     </where>
  16. </select>

這下看起來是不是舒服很多了。

set元素

現在我們想通過用戶id更新用戶信息,參數為UserModel對象,對象中的屬性如果不為空,就進行更新,我們可以這麼寫:

  1. <update id="update1" parameterType="com.javacode2018.chat05.demo8.model.UserModel">
  2.     UPDATE t_user SET
  3.     <if test="name!=null">
  4.         name = #{name},
  5.     </if>
  6.     <if test="age!=null">
  7.         age = #{age},
  8.     </if>
  9.     <where>
  10.         <if test="id!=null">
  11.             AND id = #{id}
  12.         </if>
  13.     </where>
  14. </update>

我們來看一下,當所有屬性都傳值了,sql變成了下麵這樣:

UPDATE t_user SET name = ?, age = ?, where id = ?

上面這個sql是有問題的,where前面多了一個逗號,得想辦法將這個逗號去掉,這個逗號屬於最後一個需要更新的欄位後面的逗號,屬於多餘的,mybatis中提供了set元素來解決這個問題,將上面的代碼改成下麵這樣:

  1. <update id="update1" parameterType="com.javacode2018.chat05.demo8.model.UserModel">
  2.     UPDATE t_user
  3.     <set>
  4.         <if test="name!=null">
  5.             name = #{name},
  6.         </if>
  7.         <if test="age!=null">
  8.             age = #{age},
  9.         </if>
  10.     </set>
  11.     <where>
  12.         <if test="id!=null">
  13.             AND id = #{id}
  14.         </if>
  15.     </where>
  16. </update>

我們將sql中的set去掉了,加了個set元素,set元素會對其內部拼接的sql進行處理,會將這部分sql前後的逗號給去掉併在前面加上set

當傳入id和age的時候,生成的sql:

UPDATE t_user SET age = ? where id = ?

trim元素

這個元素的功能比較強大,先看一下他的語法:

  1. <trim prefix="" prefixOverrides="" suffix="" suffixOverrides="">
  2. </trim>

trim元素內部可以包含各種動態sql,如where、chose、sql等各種元素,使用trim包含的元素,mybatis處理過程:

  1. 先對trim內部的sql進行拼接,比如這部分sql叫做sql1

  2. 將sql1字元串前面的部分中包含trim的prefixOverrides指定的部分給去掉,得到sql2

  3. 將sql2字元串後面的部分中包含trim的suffixOverrides指定的部分給去掉,得到sql3

  4. 在sql3前面追加trim中prefix指定的值,得到sql4

  5. 在sql4後面追加trim中suffix指定的值,得到最終需要拼接的sql5

瞭解了這個過程之後,說明可以通過trim來代替where和set,我們使用trim來改造一下案例1,如下:

  1. <select id="getList1" resultType="com.javacode2018.chat05.demo8.model.UserModel" parameterType="map">
  2.     SELECT id,name,age FROM t_user
  3.     <trim prefix="where" prefixOverrides="and|or">
  4.         <if test="id!=null">
  5.             AND id = #{id}
  6.         </if>
  7.         <if test="name!=null and name.toString()!=''">
  8.             AND name = #{name}
  9.         </if>
  10.         <if test="age!=null">
  11.             AND age = #{age}
  12.         </if>
  13.     </trim>
  14. </select>

註意上面的prefixOverrides的值的寫法,如果有多個需要覆蓋的之間用|進行分割,suffixOverrides寫法和prefixOverrides的寫法類似。

我們在用trim來改造一下上面的update中的,如下:

  1. <update id="update1" parameterType="com.javacode2018.chat05.demo8.model.UserModel">
  2.     UPDATE t_user
  3.     <trim prefix="SET" prefixOverrides="," suffixOverrides=",">
  4.         <if test="name!=null">
  5.             name = #{name},
  6.         </if>
  7.         <if test="age!=null">
  8.             age = #{age},
  9.         </if>
  10.     </trim>
  11.     <where>
  12.         <if test="id!=null">
  13.             AND id = #{id}
  14.         </if>
  15.     </where>
  16. </update>

上面的prefixOverrides和suffixOverrides都設置的是逗號,表示trim內部的sql前後的逗號會被去掉,最後會在前面拼接一個prefix指定的set。

大家有興趣的可以去看一下trim的java實現,代碼下麵這個類中:

org.apache.ibatis.scripting.xmltags.TrimSqlNode

實際上where和set的實現是繼承了TrimSqlNode,where對應的java代碼:

  1. public class WhereSqlNode extends TrimSqlNode {
  2.   private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n""OR\n""AND\r""OR\r""AND\t""OR\t");
  3.   public WhereSqlNode(Configuration configuration, SqlNode contents) {
  4.     super(configuration, contents, "WHERE", prefixList, null, null);
  5.   }
  6. }

set對應的 java代碼:

  1. public class SetSqlNode extends TrimSqlNode {
  2.   private static final List<String> COMMA = Collections.singletonList(",");
  3.   public SetSqlNode(Configuration configuration,SqlNode contents) {
  4.     super(configuration, contents, "SET", COMMA, null, COMMA);
  5.   }
  6. }

最後都是依靠TrimSqlNode來實現的。

foreach元素

相當於java中的迴圈,可以用來遍曆數組、集合、map等。

語法

  1. <foreach collection="需要遍歷的集合" item="集合中當前元素" index="" open="" separator="每次遍歷的分隔符" close="">
  2. 動態sql部分
  3. </foreach>
  • collection:可以是一個List、Set、Map或者數組

  • item:集合中的當前元素的引用

  • index:用來訪問當前元素在集合中的位置

  • separator:各個元素之間的分隔符

  • open和close用來配置最後用什麼首碼和尾碼將foreach內部所有拼接的sql給包裝起來。

案例:in多值查詢

我們對案例1做個改造,map中支持放入用戶的id列表(ArrayList),對應的key為idList,然後支持多個用戶id查詢,此時我們需要用in來查詢,實現如下:

  1. <select id="getList1" resultType="com.javacode2018.chat05.demo8.model.UserModel" parameterType="map">
  2.     SELECT id,name,age FROM t_user
  3.     <where>
  4.         <if test="id!=null">
  5.             AND id = #{id}
  6.         </if>
  7.         <if test="name!=null and name.toString()!=''">
  8.             AND name = #{name}
  9.         </if>
  10.         <if test="age!=null">
  11.             AND age = #{age}
  12.         </if>
  13.         <if test="idList!=null and idList.size()>=1">
  14.             <foreach collection="idList" item="id" open="AND id in (" separator="," close=")">
  15.                 #{id}
  16.             </foreach>
  17.         </if>
  18.     </where>
  19. </select>

大家看一下上面idList那部分判斷,判斷這個參數不為空,並且size()大於1,表示這個集合不為空,然後會走if元素內部的foreach元素。

比如我們傳遞的idList對應的是[1,2],最後產生的sql如下:

SELECT id,name,age FROM t_user WHERE id in ( ? , ? ) 


	
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一 、通過雲開發平臺快速創建初始化應用 1.創建相關應用模版請參考鏈接:徹底學會快速部署vue框架,一篇就夠了 2.完成創建後就可以在github中查看到新增的Vue.js 倉庫 二 、 本地編寫《開發跨平臺桌面應用》項目 1.將應用模版克隆到本地 ● 首先假定你已經安裝了Git、node,沒有安裝 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. new操作符的實現原理 new操作符的執行過程: (1)首先創建了一個新的空對象 (2)設置原型,將對象的原型設置為函數的 prototype 對象。 (3)讓函數的 this 指向這個對象,執行構造函數的代碼(為這個新對象添加屬性 ...
  • 參考資料 https://www.zhangxinxu.com/wordpress/2017/12/understand-svg-fedisplacementmap-filter/ 該文章已經講的特別細緻了,該篇僅以此記錄動效過程中各點的計算。 feDisplacementMap feDisplac ...
  • 一.同源策略問題(本地調試解決方案) firefox瀏覽器 地址欄輸入:about:config 搜索security.fileuri.strict_origin_policy(這個是安全文件同源策略限制),設置為false 重啟瀏覽器 chome瀏覽器 C盤下創建一個文件夾,名稱隨意(chrome ...
  • 大家好,我是半夏👴,一個剛剛開始寫文的沙雕程式員.如果喜歡我的文章,可以關註➕ 點贊 👍 加我微信:frontendpicker,一起學習交流前端,成為更優秀的工程師~關註公眾號:搞前端的半夏,瞭解更多前端知識! 點我探索新世界! 原文鏈接 ==>http://sylblog.xin/archi ...
  • 之前的文章已經介紹瞭如何實現Web端的即時通訊IM,為了讓大家全面的體驗通信互動的快樂。 本文介紹如何使用 ZIM SDK 快速實現實現小程式端的基本的消息收發功能,在微信中實現一個mini版微信,也就是常見的聊天功能。 ...
  • ###裝飾器模式 裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是作為現有的類的一個包裝。 這種模式創建了一個裝飾類,用來包裝原有的類,併在保持類方法簽名完整性的前提下,提供了額外的功能。 我們通過下麵的實例 ...
  • 網頁端微信掃碼支付流程 1. 用戶在網頁上選擇“微信支付” 2. 用戶輸入充值金額,點擊“確定”操作 3. 網頁根據微信通道返回的支付鏈接生成微信支付碼 4. 用戶通過手機掃碼支付 5. 網頁根據用戶付款結果,進行跳轉或提示。 如下是一個產品交互原型,便於直觀理解。 再介紹一下網頁端微信掃碼支付的時 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...