1. 程式人生 > 其它 >12.動態SQL

12.動態SQL

動態 SQL 是 MyBatis 的強大特性之一。在 JDBC 或其它類似的框架中,開發人員通常需要手動拼接 SQL 語句。根據不同的條件拼接 SQL 語句是一件極其痛苦的工作。例如,拼接時要確保添加了必要的空格,還要注意去掉列表最後一個列名的逗號。而動態 SQL 恰好解決了這一問題,可以根據場景動態的構建查詢。

動態 SQL 只有幾個基本元素,與 JSTL 或 XML 文字處理器相似,十分簡單明瞭,大量的判斷都可以在 MyBatis 的對映 XML 檔案裡配置,以達到許多需要大量程式碼才能實現的功能。

動態 SQL 大大減少了編寫程式碼的工作量,更體現了 MyBatis 的靈活性、高度可配置性和可維護性。

MyBatis 也可以在註解中配置 SQL,但是由於註解功能受限,且對於複雜的 SQL 語句來說可讀性差,所以使用較少。本教程不對它們進行介紹。

MyBatis 的動態 SQL 包括以下幾種元素,如下表所示。

元素 作用 備註
if 判斷語句 單條件分支判斷
choose(when、otherwise) 相當於 Java 中的 switch case 語句 多條件分支判斷
trim、where 輔助元素 用於處理一些SQL拼裝問題
foreach 迴圈語句 在in語句等列舉條件常用
bind 輔助元素 拼接引數

 

一、if標籤:條件判斷

MyBatis if 類似於 Java 中的 if 語句,是 MyBatis 中最常用的判斷語句。使用 if 標籤可以節省許多拼接 SQL 的工作,把精力集中在 XML 的維護上。



if 語句使用方法簡單,常常與 test 屬性聯合使用。語法如下。

  • <if test="判斷條件">
  • SQL語句
  • </if>

當判斷條件為 true 時,才會執行所包含的 SQL 語句。

最常見的場景是在 if 語句中包含 where 子句,例如。

<select id="selectAllWebsite" resultMap="myResult">
select id,name,url from website
<if test="name != null">
where name like #{name}
</if>
</select>

以上代表表示根據網站名稱去查詢相應的網站資訊,但是網站名稱是一個可填可不填的條件,不填寫的時候不作為查詢條件。

可多個 if 語句同時使用。以下語句表示為可以按照網站名稱(name)或者網址(url)進行模糊查詢。如果您不輸入名稱或網址,則返回所有的網站記錄。但是,如果你傳遞了任意一個引數,它就會返回與給定引數相匹配的記錄。

<select id="selectAllWebsite" resultMap="myResult">
select id,name,url from website where 1=1
<if test="name != null">
AND name like #{name}
</if>


<if test="url!= null">
AND url like #{url}
</if>
</select>

二、choose、when和otherwise標籤

MyBatis 中動態語句 choose-when-otherwise 類似於 Java 中的 switch-case-default 語句。由於 MyBatis 並沒有為 if 提供對應的 else 標籤,如果想要達到<if>...<else>...</else> </if> 的效果,可以藉助 <choose>、<when>、<otherwise> 來實現。

動態語句 choose-when-otherwise 語法如下。

  • <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 標籤中的判斷條件是否成立,如果有一個成立,則執行相應的 SQL 語句,choose 執行結束;如果都不成立,則執行 otherwise 中的 SQL 語句。這類似於 Java 的 switch 語句,choose 為 switch,when 為 case,otherwise 則為 default。

2.1 示例

以下示例要求:

  • 當網站名稱不為空時,只用網站名稱作為條件進行模糊查詢;
  • 當網站名稱為空,而網址不為空時,則用網址作為條件進行模糊查詢;
  • 當網站名稱和網址都為空時,則要求網站年齡不為空。


下面使用 choose-when-otherwise 標籤實現(本節示例基於《第一個MyBatis程式》一節的程式碼實現)。

WebsiteMapper.xml 程式碼如下。

<mapper namespace="net.biancheng.mapper.WebsiteMapper">
<select id="selectWebsite"
parameterType="net.biancheng.po.Website"
resultType="net.biancheng.po.Website">
SELECT id,name,url,age,country
FROM website WHERE 1=1
<choose>
<when test="name != null and name !=''">
AND name LIKE CONCAT('%',#{name},'%')
</when>
<when test="url != null and url !=''">
AND url LIKE CONCAT('%',#{url},'%')
</when>
<otherwise>
AND age is not null
</otherwise>
</choose>
</select>
</mapper>

WebsiteMapper 類中方法如下。

  • public List<Website> selectWebsite(Website website);

測試類程式碼如下。

public class Test {
public static void main(String[] args) throws IOException {


// 讀取配置檔案mybatis-config.xml
InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); // 根據配置檔案構建
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
// 通過SqlSessionFactory建立SqlSession
SqlSession ss = ssf.openSession();
Website site = new Website();
site.setname("程式設計");
List<Website> siteList = ss.selectList("net.biancheng.mapper.WebsiteMapper.selectWebsite", site);
for (Website ws : siteList) {
System.out.println(ws);
}
}
}

輸出結果如下。

DEBUG [main] - ==>  Preparing: SELECT id,name,url,age,country FROM website WHERE 1=1 AND name LIKE CONCAT('%',?,'%')
DEBUG [main] - ==> Parameters: 程式設計(String)
DEBUG [main] - <==      Total: 1
Website[id=1,name=程式設計幫,url=https://www.biancheng.net/,age=10,country=CN,createtime=null]

這樣 MyBatis 就會根據引數的設定進行判斷來動態組裝 SQL,以滿足不同業務的要求。遠比 Hibernate 和 JDBC 中大量判斷 Java 程式碼要清晰和明確。

三、where標籤

 SQL 語句中加入了一個條件“1=1”,如果沒有加入這個條件,那麼可能就會變成下面這樣一條錯誤的語句。

  • SELECT id,name,url,age,country FROM website AND name LIKE CONCAT('%',#{name},'%')

顯然以上語句會出現 SQL 語法異常,但加入“1=1”這樣的條件又非常奇怪,所以 MyBatis 提供了 where 標籤。

where 標籤主要用來簡化 SQL 語句中的條件判斷,可以自動處理 AND/OR 條件,語法如下。

  • <where>
  • <if test="判斷條件">
  • AND/OR ...
  • </if>
  • </where>

if 語句中判斷條件為 true 時,where 關鍵字才會加入到組裝的 SQL 裡面,否則就不加入。where 會檢索語句,它會將 where 後的第一個 SQL 條件語句的 AND 或者 OR 關鍵詞去掉。

3.1 示例

WebsiteMapper.xml 程式碼如下。

<select id="selectWebsite" resultType="net.biancheng.po.Website">
select id,name,url from website
<where>
<if test="name != null">
AND name like #{name}
</if>


<if test="url!= null">
AND url like #{url}
</if>
</where>
</select>

WebsiteMapper 類中方法如下。

  • public List<Website> selectWebsite(Website website);

測試類程式碼如下。

public class Test {
public static void main(String[] args) throws IOException {


// 讀取配置檔案mybatis-config.xml
InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); // 根據配置檔案構建
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
// 通過SqlSessionFactory建立SqlSession
SqlSession ss = ssf.openSession();
Website site = new Website();
site.setname("程式設計");
List<Website> siteList = ss.selectList("net.biancheng.mapper.WebsiteMapper.selectWebsite", site);
for (Website ws : siteList) {
System.out.println(ws);
}
}
}

輸出結果如下。

DEBUG [main] - ==>  Preparing: SELECT id,name,url,age,country FROM website WHERE  name LIKE CONCAT('%',?,'%')
DEBUG [main] - ==> Parameters: 程式設計(String)
DEBUG [main] - <==      Total: 1
Website[id=1,name=程式設計幫,url=https://www.biancheng.net/,age=10,country=CN]

四、set標籤

在 Mybatis 中,update 語句可以使用 set 標籤動態更新列。set 標籤可以為 SQL 語句動態的新增 set 關鍵字,剔除追加到條件末尾多餘的逗號。

4.1示例

WebsiteMapper.xml 程式碼如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="net.biancheng.mapper.WebsiteMapper">
<select id="selectWebsite" resultType="net.biancheng.po.Website">
SELECT * FROM website
<where>
<if test="id!=null and id!=''">
id=#{id}
</if>
</where>
</select>


<!--使用set元素動態修改一個網站記錄 -->
<update id="updateWebsite"
parameterType="net.biancheng.po.Website">
UPDATE website
<set>
<if test="name!=null">name=#{name}</if>
<if test="url!=null">url=#{url}</if>
</set>
WHERE id=#{id}
</update>
</mapper>

WebsiteMapper 類中方法如下。

package net.biancheng.mapper;


import java.util.List;


import org.apache.ibatis.annotations.Param;


import net.biancheng.po.Website;


public interface WebsiteMapper {
public List<Website> selectWebsite(Website site);


public int updateWebsite(Website site);
}

測試類程式碼如下。

package net.biancheng.test;


import java.io.IOException;
import java.io.InputStream;
import java.util.List;


import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;


import net.biancheng.mapper.WebsiteMapper;
import net.biancheng.po.Website;


public class Test {
public static void main(String[] args) throws IOException {
InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
SqlSession ss = ssf.openSession();


Website site = new Website();
site.setId(1);
site.setUrl("www.biancheng.net");


// 執行update語句前
List<Website> siteList = ss.getMapper(WebsiteMapper.class).selectWebsite(site);
for (Website st : siteList) {
System.out.println(st);
}


int num = ss.getMapper(WebsiteMapper.class).updateWebsite(site);
System.out.println("影響資料庫行數" + num);


// 執行update語句後
List<Website> siteList2 = ss.getMapper(WebsiteMapper.class).selectWebsite(site);
for (Website st : siteList2) {
System.out.println(st);
}
ss.commit();
ss.close();
}
}

輸出結果如下。

DEBUG [main] - ==>  Preparing: SELECT * FROM website WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Website[id=1,name=程式設計幫,url=http://www.biancheng.net/,age=10,country=CN]
DEBUG [main] - ==>  Preparing: UPDATE website SET url=? where id=?
DEBUG [main] - ==> Parameters: www.biancheng.net(String), 1(Integer)
DEBUG [main] - <==    Updates: 1
影響資料庫行數1
DEBUG [main] - ==>  Preparing: SELECT * FROM website WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Website[id=1,name=程式設計幫,url=www.biancheng.net,age=10,country=CN]

五、foreach標籤

對於一些 SQL 語句中含有 in 條件,需要迭代條件集合來生成的情況,可以使用 foreach 來實現 SQL 條件的迭代。 

Mybatis foreach 標籤用於迴圈語句,它很好的支援了資料和 List、set 介面的集合,並對此提供遍歷的功能。語法格式如下。

  • <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。

5.1示例

現有 website 表包含以下記錄。

+----+----------------+----------------------------+-----+---------+---------------------+
| id | name           | url                        | age | country | createtime          |
+----+----------------+----------------------------+-----+---------+---------------------+
|  1 | 程式設計幫         | https://www.biancheng.net/ |  10 | CN      | 2021-02-23 10:20:40 |
|  2 | C語言中文網    | http://c.biancheng.net/    |  12 | CN      | 2021-03-08 11:23:27 |
|  3 | 百度           | https://www.baidu.com/     |  18 | CN      | 2021-03-08 11:23:53 |
|  4 | 淘寶           | https://www.taobao.com/    |  17 | CN      | 2021-03-10 10:33:54 |
|  5 | Google         | https://www.google.com/    |  23 | US      | 2021-03-10 10:34:34 |
|  6 | GitHub         | https://github.com/        |  13 | US      | 2021-03-10 10:34:34 |
|  7 | Stack Overflow | https://stackoverflow.com/ |  16 | US      | 2021-03-10 10:34:34 |
|  8 | Yandex         | http://www.yandex.ru/      |  11 | RU      | 2021-03-10 10:34:34 |
+----+----------------+----------------------------+-----+---------+---------------------+

WebsiteMapper.xml 中程式碼如下。

<select id="selectWebsite"
parameterType="net.biancheng.po.Website"
resultType="net.biancheng.po.Website">
SELECT id,name,url,age,country
FROM website WHERE age in
<foreach item="age" index="index" collection="list" open="("
separator="," close=")">
#{age}
</foreach>
</select>

WebsiteMapper 類中相應方法如下。

  • public List<Website> selectWebsite(List<Integer> ageList);

測試程式碼如下。

public class Test {
public static void main(String[] args) throws IOException {
// 讀取配置檔案mybatis-config.xml
InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); // 根據配置檔案構建
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
// 通過SqlSessionFactory建立SqlSession
SqlSession ss = ssf.openSession();
List<Integer> ageList = new ArrayList<Integer>();
ageList.add(10);
ageList.add(12);
List<Website> siteList = ss.selectList("net.biancheng.mapper.WebsiteMapper.selectWebsite", ageList);
for (Website ws : siteList) {
System.out.println(ws);
}
}
}

輸出結果如下。

DEBUG [main] - ==>  Preparing: SELECT id,name,url,age,country FROM website WHERE age in ( ? , ? )
DEBUG [main] - ==> Parameters: 10(Integer), 12(Integer)
DEBUG [main] - <==      Total: 2
Website[id=1,name=程式設計幫,url=https://www.biancheng.net/,age=10,country=CN]
Website[id=2,name=C語言中文網,url=http://c.biancheng.net/,age=12,country=CN]

拓展

在使用 foreach 標籤時,應提前預估一下 collection 物件的長度。因為大量資料的 in 語句會影響效能,且還有一些資料庫會限制執行的 SQL 語句長度。

六、bind標籤

每個資料庫的拼接函式或連線符號都不同,例如 MySQL 的 concat 函式、Oracle 的連線符號“||”等。這樣 SQL 對映檔案就需要根據不同的資料庫提供不同的實現,顯然比較麻煩,且不利於程式碼的移植。幸運的是,MyBatis 提供了 bind 標籤來解決這一問題。

bind 標籤可以通過 OGNL 表示式自定義一個上下文變數。

比如,按照網站名稱進行模糊查詢,SQL 對映檔案如下。

<select id="selectWebsite" resultType="net.biancheng.po.Website">
<bind name="pattern" value="'%'+_parameter+'%'" />
SELECT id,name,url,age,country
FROM website
WHERE name like #{pattern}
</select>

bind 元素屬性如下。

  • value:對應傳入實體類的某個欄位,可以進行字串拼接等特殊處理。
  • name:給對應引數取的別名。


以上程式碼中的“_parameter”代表傳遞進來的引數,它和萬用字元連線後,賦給了 pattern,然後就可以在 select 語句中使用這個變數進行模糊查詢,不管是 MySQL 資料庫還是 Oracle 資料庫都可以使用這樣的語句,提高了可移植性。

大部分情況下需要傳遞多個引數,下面為傳遞多個引數時 bind 的用法示例。

6.1 示例

WebsiteMapper 類中方法程式碼如下。

  • public List<Website> selectWebsite(Website site);

SQL 對映檔案程式碼如下。

<select id="selectWebsite" resultType="net.biancheng.po.Website">
<bind name="pattern_name" value="'%'+name+'%'" />
<bind name="pattern_url" value="'%'+url+'%'" />
SELECT id,name,url,age,country
FROM website
WHERE name like #{pattern_name}
AND url like #{pattern_url}
</select>

測試程式碼如下。

public class Test {
public static void main(String[] args) throws IOException {
// 讀取配置檔案mybatis-config.xml
InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); // 根據配置檔案構建
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
// 通過SqlSessionFactory建立SqlSession
SqlSession ss = ssf.openSession();
Website site = new Website();
site.setname("程式設計");
site.setUrl("http");
List<Website> siteList = ss.selectList("net.biancheng.mapper.WebsiteMapper.selectWebsite", site);


for (Website ws : siteList) {
System.out.println(ws);
}
}
}

執行結果如下。

DEBUG [main] - ==>  Preparing: SELECT id,name,url,age,country FROM website WHERE name like ? AND url like ?
DEBUG [main] - ==> Parameters: %程式設計%(String), %http%(String)
DEBUG [main] - <==      Total: 1
Website[id=1,name=程式設計幫,url=https://www.biancheng.net/,age=10,country=CN]

七、trim標籤

在 MyBatis 中除了使用 if+where 實現多條件查詢,還有一個更為靈活的元素 trim 能夠替代之前的做法。

trim 一般用於去除 SQL 語句中多餘的 AND 關鍵字、逗號或者給 SQL 語句前拼接 where、set 等字尾,可用於選擇性插入、更新、刪除或者條件查詢等操作。trim 語法格式如下。

  • <trim prefix="字首" suffix="字尾" prefixOverrides="忽略字首字元" suffixOverrides="忽略字尾字元">
  • SQL語句
  • </trim>


trim 中屬性說明如下。

屬性 描述
prefix 給SQL語句拼接的字首,為 trim 包含的內容加上字首
suffix 給SQL語句拼接的字尾,為 trim 包含的內容加上字尾
prefixOverrides 去除 SQL 語句前面的關鍵字或字元,該關鍵字或者字元由 prefixOverrides 屬性指定。
suffixOverrides 去除 SQL 語句後面的關鍵字或者字元,該關鍵字或者字元由 suffixOverrides 屬性指定。

7.1示例

下面我們利用 trim 實現與 where 元素相同的效果。

要求:根據網站名稱或網址對網站進行模糊查詢。

WebsiteMapper.xml 程式碼如下。

<select id="selectWebsite" resultType="net.biancheng.po.Website">
SELECT id,name,url,age,country
FROM website
<trim prefix="where" prefixOverrides="and">
<if test="name != null and name !=''">
AND name LIKE CONCAT ('%',#{name},'%')
</if>
<if test="url!= null">
AND url like concat ('%',#{url},'%')
</if>
</trim>
</select>

WebsiteMapper 類中方法如下。

  • public List<Website> selectWebsite(Website website);

測試類程式碼如下。

public class Test {
public static void main(String[] args) throws IOException {


// 讀取配置檔案mybatis-config.xml
InputStream config = Resources.getResourceAsStream("mybatis-config.xml"); // 根據配置檔案構建
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(config);
// 通過SqlSessionFactory建立SqlSession
SqlSession ss = ssf.openSession();
Website site = new Website();
site.setname("程式設計");
site.setUrl("http");
List<Website> siteList = ss.selectList("net.biancheng.mapper.WebsiteMapper.selectWebsite", site);
for (Website ws : siteList) {
System.out.println(ws);
}
}
}

輸出結果如下。

DEBUG [main] - ==>  Preparing: SELECT id,name,url,age,country FROM website where name LIKE CONCAT ('%',?,'%') AND url like concat ('%',?,'%')
DEBUG [main] - ==> Parameters: 程式設計(String), http(String)
DEBUG [main] - <==      Total: 1
Website[id=1,name=程式設計幫,url=https://www.biancheng.net/,age=10,country=CN]