Mybatis3詳解(六)——動態SQL
1、動態SQL介紹
在使用傳統的JDBC來編寫程式碼時,很多時候需要去拼接SQL,這是一件很麻煩的事情,因為有些查詢需要許多的條件,比如在查詢使用者時,需要根據使用者名稱,年齡,性別或地址等資訊進行查詢,當不需要使用者名稱查詢時卻依然使用使用者名稱作為條件查詢就不合適了,而如果使用大量的Java進行判斷,那麼程式碼的可讀性比較差,又或者在拼接的時候,不注意哪裡少了或多了個空格、符號,都會導致錯誤。而Mybatis提供了對SQL語句動態拼接的能力,可以讓我們在 xml 對映檔案內,以標籤的形式編寫動態 SQL,完成邏輯判斷和動態拼接 SQL的功能。大量的判斷都可以在Mybatis的對映xml檔案裡面配置,以達到許多需要大量程式碼才能實現的功能,從而大大減少了程式碼量。
Mybatis動態SQL語句是基於OGNL表示式的,主要有以下幾類:
- if 標籤:簡單的條件判斷。
- where 標籤:相當於where關鍵字,並且能智慧的處理and or ,不必擔心多餘導致語法錯誤。
- set 標籤:和where標籤差不多,主要用於更新。
- trim 標籤:插入包含prefix字首、suffix字尾的內容,並且prefixOverrides去除第一個字首內容,suffixOverrides去除結尾字尾內容。
- choose、when、otherwize 標籤:相當於java 語言中的 switch 語法,與 jstl 中的choose 很類似。
- foreach 標籤:用來對一個集合進行遍歷,在使用 in 語句查詢時特別有用。
- bind 標籤:允許你在 OGNL 表示式以外建立一個變數,並將其繫結到當前的上下文。
本章我們以 User 表為例來說明:
2、if 標籤
if 標籤用來實現根據條件拼接sql語句,如果判斷引數不為null,則拼接sql,否則不拼接。判斷條件內容寫在if標籤的 test 屬性中。示例如下:
<mapper namespace="com.thr.mapper.UserMapper"> <resultMap id="userMap" type="com.thr.pojo.User"> <id property="userId" column="id"/> <result property="userName" column="username"/> <result property="userAge" column="age"/> <result property="userBirthday" column="birthday"/> <result property="userSex" column="sex"/> <result property="userAddress" column="address"/> </resultMap> <!--根據使用者名稱和地址查詢使用者資訊--> <select id="selectUserByNameAndAddress" parameterType="user" resultMap="userMap"> select * from t_user where <if test="userName!=null and userName!=''"> username = #{userName} </if> <if test="userAddress!=null and userAddress!=''"> and address = #{userAddress} </if> </select> </mapper>
上述程式碼當引數userName和userAddress都不為 null 時,拼接出的SQL語句為:select * from t_user where username = ? and address = ? 。但是如果上面的SQL語句中傳入的引數 userName 為null,則拼接出的sql語句為:select * from t_user where and address = ? ,可以明顯看到 where and 是錯誤的語法,導致報錯,又或者是傳入的兩個引數都為null,那麼拼接出的sql語句為:select * from t_user where ,這明顯也是錯誤的語法,要解決這個問題,需要用到where標籤。
3、where 標籤
<where>標籤相當於SQL語句中的where關鍵字,而且where標籤還有特殊的作用。作用如下:
- 自動向sql語句中新增where關鍵字
- 去掉第一個條件的and 或 or 關鍵字
上面的示例用where標籤改寫後示例如下:
<!--根據使用者名稱和地址查詢使用者資訊--> <select id="selectUserByNameAndAddress" parameterType="user" resultMap="userMap"> select * from t_user <where> <if test="userName!=null and userName!=''" > and username = #{userName} </if> <if test="userAddress!=null and userAddress!=''"> and address = #{userAddress} </if> </where> </select>
SQL語句等價於:select * from t_user where username = ? and address = ?
4、set 標籤
set標籤的功能和 where 標籤差不多,只是set 標籤是用在更新操作的時候,作用如下:
- 自動向修改sql語句中新增set關鍵字
- 去掉最後一個條件結尾的逗號
使用set標籤示例程式碼如下:
<!--修改使用者名稱、年齡和地址--> <update id="updateUser" parameterType="user"> update t_user <set> <if test="userName!=null and userName!=''"> username = #{userName}, </if> <if test="userAge!=null and userAge!=''"> age = #{userAge}, </if> <if test="userAddress!=null and userAddress!=''"> address = #{userAddress}, </if> </set> where id = #{userId} </update>
可以發現最後一個修改條件多了一個逗號(,),但set標籤幫我們去掉了,SQL語句等價於:update t_user SET username = ?, age = ?, address = ? where id = ?
5、trim 標籤(瞭解)
trim 元素的主要功能是可以在自己包含的內容前加上某些字首,也可以在其後加上某些字尾,與之對應的屬性是 prefix 和 suffix;可以把包含內容的首部某些內容去除,也可以把尾部的某些內容去除,對應的屬性是 prefixOverrides 和 suffixOverrides;正因為 trim 有這樣的功能,它可以用來實現 where 和 set 一樣的效果。
trim標籤的屬性:
- prefix:表示在trim標籤內sql語句加上字首
- suffix:表示在trim標籤內sql語句加上字尾
- prefixOverrides:表示去除第一個字首
- suffixOverrides:表示去除最後一個字尾
將前面where 標籤示例用trim 標籤代替:
<!--根據使用者名稱和地址查詢使用者資訊--> <select id="selectUserByNameAndAddress" parameterType="user" resultMap="userMap"> select * from t_user <!--<where> <if test="userName!=null and userName!=''" > and username = #{userName} </if> <if test="userAddress!=null and userAddress!=''"> and address = #{userAddress} </if> </where>--> <!-- 插入prefix屬性中指定的內容,並且移除首部所有指定在prefixOverrides屬性中的內容--> <trim prefix="where" prefixOverrides="and | or"> <if test="userName!=null and userName!=''" > and username = #{userName} </if> <if test="userAddress!=null and userAddress!=''"> and address = #{userAddress} </if> </trim> </select>
將前面set 標籤示例用trim 標籤代替:
<!--修改使用者名稱、年齡和地址--> <update id="updateUser" parameterType="user"> update t_user <!--<set> <if test="userName!=null and userName!=''"> username = #{userName}, </if> <if test="userAge!=null and userAge!=''"> age = #{userAge}, </if> <if test="userAddress!=null and userAddress!=''"> address = #{userAddress}, </if> </set>--> <!-- 插入prefix屬性中指定的內容,並且移除尾部所有指定在suffixOverrides屬性中的內容--> <trim prefix="set" suffixOverrides=","> <if test="userName!=null and userName!=''"> username = #{userName}, </if> <if test="userAge!=null and userAge!=''"> age = #{userAge}, </if> <if test="userAddress!=null and userAddress!=''"> address = #{userAddress}, </if> </trim> where id = #{userId} </update>
6、choose、when、otherwise 標籤
choose、when、otherwise標籤是按順序判斷其內部 when 標籤中的 test 條件出否成立,如果有一個成立,則choose結束,執行條件成立的SQL。當 choose 中所有 when 的條件都不滿足時,則執行 otherwise 中的SQL,類似於Java中的switch…case…default語句。
示例程式碼如下:
<select id="selectUserByChoose" resultType="user" parameterMap="userMap"> select * from t_user <where> <choose> <when test="userName!= null and userName!=''"> username=#{userName} </when> <when test="userAddress!= null and userAddress!=''"> and address=#{userAddress} </when> <otherwise> and age=#{userAge} </otherwise> </choose> </where> </select>
- 如果username不為空,則只用username作為條件查詢。SQL語句等價於:select * from t_user where username = ?
- 當username為空,而address不為空,則用address作為條件進行查詢。SQL語句等價於:select * from t_user where address= ?
- 當username和address都為空時,則要求以age作為條件查詢。SQL語句等價於:select * from t_user where age= ?
雖然這種場景有點不切實際,但是我們這裡主要集中如何使用這三個標籤來實現即可。
7、foreach 標籤
foreach 標籤主要用於遍歷集合。通常是用來構建 IN 條件語句,也可用於其他情況下動態拼接sql語句。
foreach標籤有以下幾個屬性:
- collection:表示要遍歷的集合元素,注意不要寫#{}。
- item:表示每次遍歷時生成的物件名(注:當傳入Map物件或Map.Entry物件的集合時,index 是鍵,item是值)。
- index:表示在迭代過程中,每次迭代到的位置。
- open:表示開始遍歷時要拼接的字串。
- close:表示結束遍歷時要拼接的字串。
- sperator:表示在每次遍歷時兩個物件直接的連線字串。
示例:如果現在有這樣的需求:我們需要查詢 t_user 表中 id 分別為1,2,4,5的使用者。所對應的sql語句有這兩條:select * from user where id=1 or id=2 or id=4 or id=5;和 select * from user where id in (1,2,4,5);。下面我們使用foreach標籤來改寫。
①、建立一個UserVo類,裡面封裝一個 List<Integer> ids 的屬性,程式碼如下:
public class UserVo { //封裝多個id private List<Integer> ids; public List<Integer> getIds() { return ids; } public void setIds(List<Integer> ids) { this.ids = ids; } }
②、foreach 來改寫 select * from user where id=1 or id=2 or id=4 or id=5;程式碼如下:
<select id="selectUserByListId" parameterType="userVo" resultMap="userMap"> select * from t_user <where> <!--加個括號 <foreach collection="ids" item="id" open="(" close=")" separator="or"> id=#{id} </foreach>--> <foreach collection="ids" item="id" separator="or"> id=#{id} </foreach> </where> </select>
測試程式碼如下:
@Test public void testSelectUserByListId(){ UserVo userVo = new UserVo(); List<Integer> ids = new ArrayList<>(); ids.add(1); ids.add(2); ids.add(4); ids.add(5); userVo.setIds(ids); List<User> userList = mapper.selectUserByListId(userVo); for (User user : userList) { System.out.println(user); } }
執行結果:
③、foreach 來改寫 select * from user where id in (1,2,4,5);將上面的對映檔案稍加修改:
<select id="selectUserByListId" parameterType="userVo" resultMap="userMap"> select * from t_user <where> <foreach collection="ids" item="id" open="id in (" close=")" separator=","> #{id} </foreach> </where> </select>
執行結果:
8、bind 標籤(瞭解)
bind 標籤允許你在 OGNL 表示式以外建立一個變數,並將其繫結到當前的上下文(可定義多個)。示例程式碼如下:
<!-- 模糊查詢,根據username欄位查詢使用者--> <select id="selectUserByName" parameterType="string" resultMap="userMap"> <bind name="pattern" value="'%'+_parameter+'%'"/> select * from t_user where username like #{pattern} </select>
這裡的”_parameter”代表的是傳遞進來的引數,它和萬用字元(%)連線後賦給了pattern,SQL語句等價於:select * from t_user where username like ?。這種方式無論是Mysql還是Oracle都可以使用這樣的語句,提高了程式碼的可移植性。如果傳遞了多個引數,則可以定義多個bind 標籤。
<select id="selectUserByNameAndAddress" parameterType="user" resultMap="userMap"> <bind name="pattern_username" value="'%'+userName+'%'"/> <bind name="pattern_address" value="'%'+userAddress+'%'"/> select * from t_user where username like #{pattern_username} and address like #{pattern_address} </select>