Mybatis-動態SQL的理解及使用

動態SQL

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

在博主看來,所謂動態SQL就是可以在不同的條件下拼接出不同的SQL語句。

Mybatis中主要包含以下這些設置動態SQL的標籤:

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

數據庫環境準備:

CREATE DATABASE mybatis;

USE mybatis;

CREATE TABLE `blog` (
  `id` varchar(50) NOT NULL COMMENT '博客id',
  `title` varchar(100) NOT NULL COMMENT ' 博客標題',
  `author` varchar(30) NOT NULL COMMENT ' 博客作者',
  `create_time` datetime NOT NULL COMMENT ' 創建時間',
  `views` int(30) NOT NULL COMMENT ' 瀏覽量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `blog`(`id`,`title`,`author`,`create_time`,`views`) values
('46931d7526684bf5907a364bef060c49','Java基礎','Ara_Hu','2020-03-08 21:36:28',999),
('94c7f67852df4b0f8a5b3622bb019a37','HTML基礎','Ara_Hu','2020-03-08 21:36:28',1999),
('0180aead57b94a9c891a65dadf3c4f70','MYSQL基礎','Ara_Hu','2020-03-08 21:36:28',999),
('7604b27e80f249dbb293ac44a07eaddd','JSP基礎','Ara_Hu','2020-03-08 21:36:28',3999),
('94c61b1e77a24866bfb0663ec3b3f914','Java環境搭建','Admin-TY','2020-03-08 21:38:58',9999),
('cae412bd87f040f7a95ea2e72c445630','HTML入門','Admin-TY','2020-03-08 21:38:58',9299),
('e496001fdb0d43eaad4c9dd40c079af2','MYSQL進階','Admin-TY','2020-03-08 21:38:58',9999),
('393220b217264af2bd711defa3b97219','JSP常用指令','Admin-TY','2020-03-08 21:38:58',999);

這裏我們爲了更直觀的看到執行的SQL語句,建議在Mybatis配置文件中打開日誌功能,這樣更能直觀的感受到我們的動態SQL的執行:

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>  
</settings>

其它配置就不予贅述了。

if標籤

這對於我們學了這麼久的程序來說,理解起來並不難,它就是判斷的標籤,根據if標籤中的條件是否成立,來拼接if中的SQL語句。

我們還是直接看一段具體的例子:

BlogMapper接口中的演示方法:


/**
 * 查詢博客列表
 * 採用if標籤
 *
 * @param map 傳入的map集合
 *            如果不含任何key,就全部查詢
 *            如果含有title 不含有author 就按照title查詢
 *            如果含有author 不含有title 就按照author查詢
 *            如果含有author 而且含有title 就按照author和title的條件查詢
 * @return 返回對應的Blog列表
 */
List<Blog> queryBlogIf(Map map);

BlogMapper.xml文件實現上述方法的SQL:

<select id="queryBlogIf" parameterType="map" resultType="Blog">
    select * from blog where 1 = 1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

這裏的<if>標籤就像是JSTL中的標籤一樣,test屬性就是用來設置判斷的條件。
上述xml中的<if>標籤的意思就是,如果title值不爲空,就拼接其中的SQL,後者的author一致。

測試代碼如下:

@Test
public void queryBlogIfTest() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    map.put("title", "HTML基礎");
    map.put("author", "Ara_Hu");

    List<Blog> blogs = blogMapper.queryBlogIf(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }

    sqlSession.close();
}

感興趣的讀者可以自行測試,將map添加值的兩句代碼分別省略和同時省略,還有都不省略,都會產生不一樣的效果,特別注意它每次運行的SQL語句。

  • <if>標籤
    • 如果<if>的條件成立,返回<if>中的SQL
    • 如果<if>的條件不成立,返回空

choose標籤

<choose>標籤搭配的還有另外兩個標籤,<when><otherwise>,它們就相當於switch語句,其對應方式如下:

  • <choose>:switch
  • <when>:case
  • <otherwise>:default

舉例來說明吧

BlogMapper接口中添加方法:

/**
 * 查詢博客列表
 * choose標籤
 *
 * @param map 傳入的map集合(至少含有title、author或者views其中的一個)
 *            如果含有title 就按照title條件查詢
 *            如果含有author 就按照author條件查詢
 *            如果含有views 就按照views條件查詢
 * @return 返回對應的Blog列表
 */
List<Blog> queryBlogChoose(Map map);

BlogMapper.xml中實現的查詢語句:

<select id="queryBlogChoose" parameterType="map" resultType="Blog">
    select * from blog where 1 = 1
      <choose>
           <when test="title != null">
               and title = #{title}
           </when>
           <when test="author != null">
               and author = #{author}
           </when>
           <otherwise>
               and views = #{views}
           </otherwise>
       </choose>
</select>

這裏就是說,它會進行判斷選擇,先看傳入Map中的title是否爲空,如果不爲空,就拼接title條件的查詢語句,這裏只要拼接上了,就不會再向下判斷,如果爲空就進行對author的判斷,如果author不爲空就拼接author條件的SQL,這裏也是一樣,拼接上了就結束拼接,如果前兩個判斷都不滿足,就拼接<otherwise>中的sql語句。

測試代碼:

@Test
public void queryBlogChooseTest() {
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    map.put("views", 999);
    
    //map.put("title","Java基礎");
    map.put("author","Ara_Hu");
    
    List<Blog> blogs = blogMapper.queryBlogChoose(map);

    for (Blog blog : blogs) {
        System.out.println(blog);
    }

    sqlSession.close();
}

感興趣的讀者可以對上述的代碼進行測試,分別註釋添加title和author的代碼即可,注意觀察它拼接執行的SQL語句。

  • <choose>標籤
    • 進入 <choose>後就會按順序執行
    • 當遇到一個<when>滿足其中的條件時,返回這個<when>其中的SQL
    • 如果所有的<when>都不滿足,則返回<otherwise>中的SQL
    • 如果所有的<when>都不滿足,並且也沒有<otherwise>,就返回空

trim標籤

<trim>標籤似乎有點難以理解,我們先看兩個基於它實現的<where><set>標籤。

我們發現前面的SQL語句在拼接時,我們都加了一句where 1 = 1,我們動態拼接SQL語句,如果不添加這句,後面拼接出的SQL語句就會有問題,就會產生例如select * from blog and author = ?的SQL語句,這樣的SQL語句就不能正常的執行,於是Mybatis提出了<trim>標籤來解決這個問題。

where

強行的解釋它是幹什麼的,博主並不擅長,還是舉例說明吧,我們來對上面queryBlogIf進行改進,我們去掉SQL語句中的where 1 = 1

<!--  queryBlogIf改進版  -->
<select id="queryBlogIf" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

這裏的添加where,它的作用就是檢查where中的sql語句,
如果其中的title不爲空,就會在 title = ?前面添加where從而拼接爲 where title = ? ....
如果title爲空,在<where>留下的SQL片段就是and author = ?<where>標籤就會將and去掉,從而拼接爲where author = ?

<where>標籤的原型就是如下:

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

如果Mybatis發現在<where>中的sql,它會自動在這段SQL之前拼接where(前綴爲where),如果這段SQL是and或者or開頭的,它就會將and或者or去掉(and 或者or 都會被覆蓋)。

  • <where>標籤
    • 如果<where>中的SQL爲空,則爲空
    • 如果 <where>中的SQL不爲空,則在這個SQL之前添加where
    • 如果 <where>中的SQL是以and或者or開頭,則去掉這個and或者or
set

<set>標籤一般在修改語句中用到,還是直接舉例說明:

BlogMapper接口中添加方法:

/**
 * 根據傳入的map集合更新博客信息
 * set標籤
 *
 * @param map 傳入的map集合至少包含id和(title、author、views)字段中的一個
 * @return 返回受影響的行數
 */
int updateBlog(Map map);

BlogMapper.xml中的SQL實現:

<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author},
        </if>
        <if test="views != null">
            views = #{views}
        </if>
    </set>
    where id = #{id};
</update>

測試代碼:

@Test
public void updateBlogTest(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

    HashMap map = new HashMap();
    map.put("id","46931d7526684bf5907a364bef060c49");
    
    map.put("title","Java基礎");
    map.put("author","Ara_Hu");
    map.put("views",9999);        
    
    
    int i = blogMapper.updateBlog(map);
    
    sqlSession.commit();
    
    System.out.println(i);
    
    sqlSession.close();
}

測試結果讀者可以自行觀察一下,嘗試註釋title、author和views其中的任意兩個,分別查看執行的SQL語句。

這裏<set>解決的就是字段設置之後的,的問題,如果<set>中的SQL的結果是,結尾,整個SQL就會拼接成update blog xxx = ?, where id = ?這樣的SQL顯然就有問題,Mybatis爲了解決這個問題,就提出了<set>標籤來解決它,凡是在<set>中的SQL語句,如果其中的SQL語句不爲空,它就會在這段SQL之前添加set,如果其中的SQL是,結尾,它就會省略這個,

<set>標籤的<trim>原型如下:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

這個就表示,在該標籤中的SQL如果不爲空,就以set開頭(set爲前綴),如果這個SQL語句的是以,結尾,則將這個,省略(後綴自動被覆蓋)。

  • <set>標籤:
    • 如果<set>中的SQL片段爲空,則爲空
    • 如果<set>中的SQL片段不爲空,則在開頭添加set
    • 如果<set>中的SQl片段是以,結尾,則去掉這個,

相信讀者看到這裏,對<trim>標籤就有一定的理解了。

foreach標籤

這個標籤,一看就知道是循環的標籤,當然,它就是表示循環的,舉個例子吧。

BlogMapper接口添加的方法:

/**
 * 查詢map中指定集合中的Id文章列表
 *
 * @param map 指定的Map集合 
 *            這個map集合中需要包含集合ids 用於展示文章列表的Id
 * @return 返回對應的文章列表
 */
List<Blog> queryBlogForeach(Map map);

BlogMapper.xml實現的SQL:

<select id="queryBlogForeach" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <if test="ids != null">
            id in
            <foreach collection="ids" item="id" open="(" separator="," close=")">
                #{id}
            </foreach>
        </if>
    </where>
</select>

這裏使用了幾個標籤的嵌套:

  • 首先是<where>
    • 如果在<where>中的SQL爲空,整個SQL就是select * from blog
    • 如果在<where>中的SQL不爲空,整個SQL就是select * from blog where ...
  • 其次是<if>
    • 如果<if>中的的判斷不成立,則這句SQL就爲空。
    • 如果<if>中的判斷成立,這個SQL就是select * from blog where id in ...
  • 然後是<foreach>
    • 當執行到<foreach>中,說明ids不爲空,可以循環遍歷。
    • collection:表示我們需要遍歷的集合
    • item:表示我們每次遍歷取出的變量名
    • open:第一個需要添加的前綴
    • separator:表示每個遍歷元素的分隔符
    • close:表示結尾用什麼包裹

測試代碼:

@Test
public void queryBlogForeachTest(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);

    ArrayList<String> ids = new ArrayList<String>();
    ids.add("46931d7526684bf5907a364bef060c49");
    ids.add("94c7f67852df4b0f8a5b3622bb019a37");
    ids.add("0180aead57b94a9c891a65dadf3c4f70");
    ids.add("e496001fdb0d43eaad4c9dd40c079af2");

    HashMap map = new HashMap();
    map.put("ids",ids);

    List<Blog> blogs = blogMapper.queryBlogForeach(map);

    for (Blog blog : blogs) {
        System.out.println(blog);
    }

    sqlSession.close();
}

感興趣的讀者可以自行嘗試,然後注意看SQL的執行語句。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章