MyBatis筆記(四)——動態SQL元素(if,where,set,trim,choose,when,otherwise,foreach),SQL片段

MyBatis筆記(四)——動態SQL元素(if,where,set,trim,choose,when,otherwise,foreach),SQL片段


參考: https://www.bilibili.com/video/BV1NE411Q7Nx

視頻原作者CSDN: https://blog.csdn.net/qq_33369905

這篇是自己整理了一下,以便自己複習。

0.準備

博客表:ID(主鍵),標題,內容,創建時間,更新時間,瀏覽量,是否推薦

DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog` (
  `id` varchar(50) NOT NULL,
  `title` varchar(30) NOT NULL,
  `type` varchar(20) NOT NULL,
  `content` tinytext,
  `create_time` datetime NOT NULL,
  `update_time` datetime NOT NULL,
  `views` int(7) NOT NULL,
  `recommend` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `blog` VALUES ('1', 'MyBatis學習筆記(一)', 'MyBatis', null, '2020-05-12 16:19:26', '2020-05-12 16:19:32', '20', '');
INSERT INTO `blog` VALUES ('2', 'Spring學習筆記(二)', 'Spring', null, '2020-05-13 16:19:54', '2020-05-13 16:19:57', '39', '');
INSERT INTO `blog` VALUES ('3', 'SpringMVC學習筆記(一)', 'SpringMVC', null, '2020-05-13 16:20:34', '2020-05-12 16:20:34', '19', '');

建立對應的JavaBean Blog

//博客表:ID(主鍵),標題,內容,創建時間,更新時間,瀏覽量,是否推薦
public class Blog {
    private String id;
    private String title;
    private String content;
    private Date createTime;
    private Date updateTime;
    private int views;
    private Boolean recommend;
//Lombok @Data
//無參構造/有參構造
//getter/setter
//toString
}

由於有字段:

createTime -> create_time

update_time -> updateTime

開啓駝峯命名 MyBatis-confg.xml中添加設置:

注意:settings需要寫在properties之後

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

1.動態SQL

概念:動態SQL就是根據不同的條件生成不同的SQL語句

文檔推薦:MyBatis官方文檔(中文)-動態SQL⚡️

以下三段文字摘自上述MyBatis官方文檔:

動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。

使用動態 SQL 並非一件易事,但藉助可用於任何 SQL 映射語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。

如果你之前用過 JSTL 或任何基於類 XML 語言的文本處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間瞭解大量的元素。藉助功能強大的基於 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

2. if 元素

2.1.if標籤 使用場景舉例:查詢條件是可選項

使用場景舉例,博客搜索,用戶可以博客文章名稱,分類名稱,是否推薦三個選項任意組合來查詢博客:

比如:

  • 只選擇分類和推薦的條件來查詢
  • 選擇博客標題和推薦的條件來查詢

總之就是三個條件都可有可無,這樣的查詢就需要使用動態SQL來解決了,以往的做法是使用Java代碼去判斷條件後,選擇性的拼接SQL語句,這樣做很麻煩。

MyBatis動態SQL場景博客搜索

2.2 編寫接口類與對應的mapper.xml

思路分爲三步:原始的完整SQL,select基礎標籤下的完整SQL,添加if標籤

2.2.1 首先把完整的SQL寫好

測試如圖:博客名稱搜索關鍵字 筆記 ,分類爲 MyBatis ,不推薦(recommend= 0):

select * from blog where title LIKE '%筆記%' and type = 'MyBatis'  and recommend= 0;

MyBattis動態SQL舉例

2.2.2 基礎的select標籤下的SQL

接口類BlogMapper:

public interface BlogMapper {
    List<Blog> selectBlogIf(Map map);
}

BlogMapper.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="com.piao.dao.BlogMapper">
    
<select id="selectBlogIf" parameterType="map" resultType="com.piao.pojo.Blog">
 select * from blog where title LIKE #{title} and type = #{type} and recommend=#{recommend};
</select>
    
</mapper>
2.2.3 添加if元素

必選項直接寫在where後面,可選項寫在if元素中

但這個例子有點特殊,三個都是可選項,所以手動加一個必選項條件 1=1(反正會成立),不加的話where後面可能會直接連接and,sql語法錯誤。

<?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="com.piao.dao.BlogMapper">

    <!--需求:
    根據 博客名稱 , 分類名稱 , 是否推薦 來查詢博客!
    map傳參的key有title,type,recommend
    -->
    <select id="selectBlogIf" parameterType="map" resultType="com.piao.pojo.Blog">
        select * from blog where 1=1
        <if test="title != null">
            and title LIKE #{title}
        </if>
        <if test="author != null">
            and type = #{type}
        </if>
        <if test="recommend != null">
            and recommend=#{recommend};
        </if>

    </select>

</mapper>

2.3測試if元素

public class BlogMapperTest {
    static SqlSession session;
    static BlogMapper blogMapper;

    //測試前初始化SqlSession 與mapper
    @Before
    public void init() {
        session = MybatisUtils.getSession();//獲取sqlsession
        //session.selectList("com.kuang.mapper.UserMapper.selectUser");//獲取session中的mapper 方法一
        blogMapper = session.getMapper(BlogMapper.class);//獲取session中的mapper 方法二(一般用這個)
    }

    //測試後統一關閉SqlSession
    @After
    public void closeSqlSession() {
        //關閉sqlsession
        session.close();
    }

    //動態SQL map傳參的key有title,type,recommend
    @Test
    public void testSelectUser() {
        Map<String, Object> map = new HashMap<>();
        map.put("title","%筆記%");
//        map.put("type","MyBatis");
        map.put("recommend",0);
        List<Blog> blogs = blogMapper.selectBlogIf(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
    }
}

testSelectUser方法只傳了title和recommend,即博客名稱含有 筆記 兩個字的 且沒有推薦的博客。

結果如下:

Blog{id='1', title='MyBatis學習筆記(一)', content='內容', createTime=Tue May 12 16:19:26 CST 2020, updateTime=Tue May 12 16:19:32 CST 2020, views=20, commend=false}
Blog{id='3', title='SpringMVC學習筆記(一)', content='內容', createTime=Wed May 13 16:20:34 CST 2020, updateTime=Tue May 12 16:20:34 CST 2020, views=19, commend=false}

3.where元素與set元素,自定義trim元素

3.1where 元素

以上述的if標籤的例子進行修改:

<!--    修改前-->   
    <select id="selectBlogIf" parameterType="map" resultType="com.piao.pojo.Blog">
        select * from blog where 1=1
        <if test="title != null">
            and title LIKE #{title}
        </if>
        <if test="author != null">
            and type = #{type}
        </if>
        <if test="recommend != null">
            and recommend=#{recommend};
        </if>
    </select>

<!--    修改後-->  
    <select id="selectBlogWhere" parameterType="map" resultType="com.piao.pojo.Blog">
        select * from blog
        <where>
            <if test="title != null">
                AND title LIKE #{title}
            </if>
            <if test="author != null">
                AND type = #{type}
            </if>
            <if test="recommend != null">
                AND recommend=#{recommend};
            </if>
        </where>
    </select>

可看到,就是把SQL語句的where關鍵字變成where標籤,包裹住後面的if標籤而已。

作用:

  • 如果它包含的標籤中有返回值的話,它就插入一個‘where’。

比如title爲null則沒有 title LIKE #{title} 這個條件語句,title,author,recommend只要有一個不爲null則會插入where。若三個均爲null,則會去掉where。SQL變爲select * from blog

  • 如果標籤返回的內容是以AND 或OR 開頭的,則它會剔除掉。 而if元素沒這個功能所以需要where有必選條件。

比如上述title和author爲null,而recommend不爲null,會自動去掉and,使得SQL變爲:

select * from blog where recommend=#{recommend};

3.2 set 元素

和where寫在一起,是因爲作用基本一致,只是set用於更新語句中而已。

注意set用逗號隔開,而where用and/or隔開

舉例:根據id更新博客的標題,分類,是否推薦

接口:

    //set測試 與where類似
    void updateBlogBySet(Map map);

xml:

    <!--  set測試 根據id更新博客 參數均爲可選項,使用if元素
    update blog set title = #{title},type = #{type},recommend = #{recommend} where id = #{id};
    -->
    <update id="updateBlogBySet" parameterType="map">
        update blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="type != null">
                type = #{type},
            </if>
            <if test="recommend != null">
                recommend = #{recommend}
            </if>
        </set>
        where id = #{id};
    </update>

測試

    //測試set元素
    @Test
    public void updateBlogBySet() {
        Map<String, Object> map = new HashMap<>();
        map.put("title", "筆記");
        map.put("type", "MyBatis");
        map.put("recommend", 1);
        map.put("id", 1);
        blogMapper.updateBlogBySet(map);
        session.commit();//SqlSession提交事務
    }

3.3自定義trim元素

自定義trim元素 摘自MyBatis官方文檔: https://mybatis.org/mybatis-3/zh/dynamic-sql.html

3.3.1 與where元素等價的自定義 trim 元素

如果 where 元素與你期望的不太一樣,你也可以通過自定義 trim 元素來定製 where 元素的功能。比如,和 where 元素等價的自定義 trim 元素爲:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 屬性會忽略通過管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子會移除所有 prefixOverrides 屬性中指定的內容,並且插入 prefix 屬性中指定的內容。

3.3.2 與 set 元素等價的自定義 trim 元素
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意,我們覆蓋了後綴值設置,並且自定義了前綴值。

4. choose元素,配合when和otherwise標籤

多個條件中選擇一個使用

有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。

將上述if標籤舉例的場景變爲:傳入了 “title” 就按 “title” 查找,傳入了 “type” 就按 “type” 查找的情形。若兩者都沒有傳入,recommend=1的博客(即前兩個都沒傳參數,就返回推薦的博客)。

這個和之前if標籤例子區別:

  • 一個是 三個條件任意組合

  • 而這個是 前兩個滿足一個(且優先是博客名稱),否則直接返回推薦的博客

<select id="selectBlogChoose" parameterType="map" resultType="com.piao.pojo.Blog">
        select * from blog where
  <choose>
    <when test="title != null">
       title like #{title}
    </when>
        <when test="type != null">
       type = #{type}
    </when>
    <otherwise>
       recommend = 1;
    </otherwise>
  </choose>
</select>

與Java中swith是類似邏輯,以下是用僞代碼表示:

//僞代碼  
//choose->switch 
//when->case 
//otherwise -> default 
switch(map參數){
    //以博客名稱爲優先條件
    case 有博客名) :
       select * from blog where title like #{title};
       break;//不用管後面的了
        
    case (沒有博客名稱)有分類名稱 :
       select * from blog where type = #{type};
       break; //不用管後面的了
        
    ...其他case
    
    default : 
        //前面都不滿足,默認執行這個 查詢推薦的博客
       select * from blog where recommend = 1;
}

5.foreach元素

動態 SQL 的另一個常見使用場景是對集合進行遍歷(尤其是在構建 IN 條件語句的時候)。

foreach 元素的功能非常強大,它允許你指定一個集合,聲明可以在元素體內使用的集合項(item)和索引(index)變量。它也允許你指定開頭與結尾的字符串以及集合項迭代之間的分隔符。這個元素也不會錯誤地添加多餘的分隔符,看它多智能!

提示 你可以將任何可迭代對象(如 List、Set 等)、Map 對象或者數組對象作爲集合參數傳遞給 foreach。當使用可迭代對象或者數組時,index 是當前迭代的序號,item 的值是本次迭代獲取到的元素。當使用 Map 對象(或者 Map.Entry 對象的集合)時,index 是鍵,item 是值。

解決SQL 中 in 中遍歷的問題,因爲通常會傳遞一個集合,以往要使用Java來遍歷改集合中的元素,然後拼接SQL語句。例如下面的SQL語句:傳遞一個集合{3,5,9}

-- 查詢學號爲3,5,9的博客的標題,分類,是否推薦
SELECT title,type,recommend FROM blog where id IN(3,5,9);
-- 等價於
SELECT title,type,recommend FROM blog where id =3 OR id =5 OR id =9;
-- 也等價於:SELECT id,name,major FROM blog where 1=1 and (id=3 or id=5 or id=9);

mapper接口:

List<Blog> queryBlogForeach(Map map);

SELECT title,type,recommend FROM blog where id IN(3,5,9);

或者其等價SQL:SELECT title,type,recommend FROM blog where id =3 OR id =5 OR id =9;

(3,5,9)以集合形式保存在map中 key爲idList
foreach要做的就是 遍歷idList對應的value獲取值

foreah使用:

  • collection:指定輸入對象中的集合屬性 上述例子爲 idList
  • item:每次遍歷生成的對象取的名稱 上述例子中可取名id(自定義取名,後面用#{}獲取)
  • open:開始遍歷時的拼接字符串 上述例子爲 左括號 (
  • close:結束時拼接的字符串 上述例子爲 右括號 )
  • separator:遍歷對象之間需要拼接的字符串 上述例子爲 逗號 ,
    <!--SELECT title,type,recommend FROM blog where id IN(3,5,9);-->
    <select id="selectBlogForeach" parameterType="map" resultType="com.piao.pojo.Blog">
        SELECT title,type,recommend FROM blog
        <where>
            id in
            <foreach collection="idList" item="id" open="(" close=");" separator=",">
                    #{id}
            </foreach>
        </where>
    </select>

<!-- 若採取它的的等價sql:
SELECT title,type,recommend FROM blog where id =3 OR id =5 OR id =9;
那麼foreach寫法略有不同
-->
    <select id="selectBlogForeach" parameterType="map" resultType="com.piao.pojo.Blog">
        SELECT title,type,recommend FROM blog
        <where>
            <foreach collection="idList" item="id" open="(" close=");" separator="OR">
                id = #{id}
            </foreach>
        </where>
    </select>

測試:

public class BlogMapperTest {
    static SqlSession session;
    static BlogMapper blogMapper;

    //測試前初始化SqlSession 與mapper
    @Before
    public void init() {
        session = MybatisUtils.getSession();//獲取sqlsession
        //session.selectList("com.kuang.mapper.UserMapper.selectUser");//獲取session中的mapper 方法一
        blogMapper = session.getMapper(BlogMapper.class);//獲取session中的mapper 方法二(一般用這個)
    }

    //測試後統一關閉SqlSession
    @After
    public void closeSqlSession() {
        //關閉sqlsession
        session.close();
    }

    @Test
    public void testSelectBlogForeach() {
        HashMap map = new HashMap();
        List<Integer> idList = new ArrayList<>();
        idList.add(3);
        idList.add(5);
        idList.add(9);
        map.put("idList", idList);
        List<Blog> blogs = blogMapper.selectBlogForeach(map);
        
        System.out.println("foreach元素測試");
        //輸出 title,type,recommend
        for (Blog blog : blogs) {
            System.out.println("title="+blog.getTitle()+",type="+blog.getRecommend()+",recommend="+blog.getRecommend());
        }

    }
}

日誌及輸出:因爲數據表裏只有id爲1,2,3的博客,所以只有id爲3的數據

DEBUG [main] - ==>  Preparing: SELECT title,type,recommend FROM blog WHERE id in ( ? , ? , ? ); 
DEBUG [main] - ==> Parameters: 3(Integer), 5(Integer), 9(Integer)
TRACE [main] - <==    Columns: title, type, recommend
TRACE [main] - <==        Row: SpringMVC學習筆記(一), SpringMVC, 0
DEBUG [main] - <==      Total: 1
foreach元素測試
title=SpringMVC學習筆記(一),type=false,recommend=false

第二種等價sql測試的日誌和輸出結果:

DEBUG [main] - ==>  Preparing: SELECT title,type,recommend FROM blog WHERE ( id = ? OR id = ? OR id = ? ); 
DEBUG [main] - ==> Parameters: 3(Integer), 5(Integer), 9(Integer)
TRACE [main] - <==    Columns: title, type, recommend
TRACE [main] - <==        Row: SpringMVC學習筆記(一), SpringMVC, 0
DEBUG [main] - <==      Total: 1
foreach元素測試
title=SpringMVC學習筆記(一),type=false,recommend=false

完整代碼可查看: https://gitee.com/lipiaoMKX/SSM-study

拓展 1 trim元素常用場景 insert語句

一般插入多個數據,但都是可選項,使用自定義trim元素包裹最合適,就是

--majorid和majorname都是可選項
INSERT INTO `major`(`majorid`,`majorname`) values(9,'大數據科學');
INSERT INTO `major`(`majorid`,) values(9);
INSERT INTO `major`(`majorname`) values('大數據科學');

可用以下insert元素和trim元素實現:

<insert id="insertBlogByTrim" parameterType="">
       <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="majorid != null">
                majorid,
            </if>
            <if test="majorname != null">
                majorname,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="majorid != null">
                #{adminUserId},
            </if>
            <if test="majorname != null">
                #{loginUserName},
            </if>
        </trim>
</insert>  

拓展2 SQL片段

在***Mapper.xml中可用sql元素提取重複使用的sql片段,在需要複用的地方使用include元素引用即可,類似於c/c++頭文件的意思。

  • sql元素使用 id 來唯一標識該sql片段
  • include元素中使用 refid 來指定引用的sql片段

例如以下代碼:

  <sql id="Base_Column_List">
    category_id, category_name, category_icon, category_rank, is_deleted, create_time
  </sql>

  <select id="findCategoryList" parameterType="Map" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from tb_blog_category
        where is_deleted=0
        order by category_rank desc,create_time desc
        <if test="start!=null and limit!=null">
            limit #{start},#{limit}
        </if>
 </select>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章