動態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的執行語句。