【MyBatis學習總結 (五),動態SQL】
在之前的四篇文章中,我們已經學了MyBatis不少東西,剩下能簡單學的也不多了, MyBatis的學習也漸漸接近尾聲。
以下是之前文章的地址
(MyBatis學習總結(三),ResultMap & 日誌、分頁)
動態SQL這個點,我們作為MyBatis學習的壓軸部分,寫在倒數第二篇文章中。
動態SQL也不是三言兩語能夠講清楚的,需要一定的篇幅,所以本篇文章只寫動態SQL。
動態SQL
動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記新增必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。
如果你之前用過 JSTL 或任何基於類 XML 語言的文字處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間瞭解大量的元素。藉助功能強大的基於 OGNL 的表示式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
在涉及動態sql之前我們先搭建一下環境
搭建動態sql環境
建立表
create table `blog` (
`id` varchar(50) not null comment '部落格id',
`title` varchar(100) not null comment '部落格標題',
`author` varchar(30) not null comment '作者',
`createTime` datetime not null comment '建立時間',
`views` int(30) not null comment '瀏覽量'
) engine=InnoDB default charset=utf8;
建立完表後,我們不再使用sqlyog來直接插入資料,折騰一下使用Java程式碼來插入資料
編寫實體類
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
// 省略了getter、setter、toString方法,和構造方法
編寫mapper介面
public interface BlogMapper {
// 在mapper介面中寫對應的新增資料方法
int addBlog (Blog blog);
}
編寫對應的mapper.xml檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.molu.mapper.BlogMapper">
<insert id="addBlog" parameterType="Blog">
insert into mybatis.blog (id,title,author,createTime,views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
</mapper>
我們再寫一個工具類,使用UUID來生成隨機的 blogId
工具類
public class BlogGetId {
public static String getId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
在測試類中插入資料
public class MapperTest {
@Test
public void addBlogTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
// 插入資料
mapper.addBlog(new Blog(BlogGetId.getId(),"Java入門","moluu",new Date(),9999));
mapper.addBlog(new Blog(BlogGetId.getId(),"MySQl入門","lin",new Date(),1000));
mapper.addBlog(new Blog(BlogGetId.getId(),"Mybatis入門","moluu",new Date(),9999));
mapper.addBlog(new Blog(BlogGetId.getId(),"Spring入門","lin",new Date(),9999));
// 進行增刪改操作,一定要記得提交事務。否則插入不了
sqlSession.commit();
sqlSession.close();
}
}
插入成功
如果插入不成功的話檢查一下配置檔案是否有對其進行繫結,mapper.xml中的sql是否寫得有問題,是否漏掉了提交........
到這裡環境就準備好了
簡單使用
對於一些複雜的查詢,我們可能會指定多個查詢條件,但是這些條件可能存在也可能不存在,如果不使用持久層框架我們可能需要自己拼接SQL語句,十分的痛苦。不過MyBatis提供了動態SQL的功能來解決這個問題。
IF
我們先用一用最簡單的if
<mapper namespace="com.molu.mapper.BlogMapper">
<insert id="addBlog" parameterType="Blog">
insert into mybatis.blog (id,title,author,createTime,views)
values (#{id},#{title},#{author},#{createTime},#{views});
<!-- if 一般定義在 crud標籤體中-->
<if test="">
</if>
</insert>
</mapper>
現在我們想要實現一個功能,如果我們不對查詢條件進行限制則返回所有的部落格,如果我們對條件進行限制 則返回對應條件的部落格。
使用 if實現該功能
- 在mapper介面中寫對應的方法
public interface BlogMapper {
// 根據條件返回查詢結果
List<Blog> queryBlogIF(Map map);
}
- 修改mapper.xml檔案
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog where 1=1
<if test="title != null">
and title = #{title};
</if>
<if test="author != null">
and author = #{author};
</if>
</select>
我們使用了 if標籤
,對查詢 sql進行了簡單的條件追加。
- 編寫測試類
public class MapperTest {
@Test
public void queryBlogIFTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
}
我們往查詢程式碼中傳入了一個空map,也就是不追加任何條件。
由於我們在 where子句寫了個 1=1 的條件,該條件是一定能夠被滿足的,執行後自然會返回所有的部落格。
- 嘗試往 map中 put值(追加條件)
public class MapperTest {
@Test
public void queryBlogIFTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
// 現在 title有值了
map.put("title","Java入門");
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
}
可以看到,在追加條件後 會返回對應條件的資料。
也就是通過 if標籤,在 test條件滿足時 往where子句中追加了條件。這就是最簡單的動態sql的使用
<if test="title != null">
<!--test條件也很簡單,title有值就滿足了-->
and title = #{title};
<!--進而對 where子句進行sql的拼接,即追加條件-->
</if>
我們再玩一玩 if
現在我們查詢一下 author為 ‘lin’ 的部落格,也很簡單,往 map中 put一下 author的值即可。
//map.put("title","Java入門");
map.put("author","lin");
查詢結果:
很實用也很簡單的一個標籤不是麼?
Where
where標籤能夠使我們的sql拼接時,避免一些錯誤
在正常的開發中,我們是不會在where子句後面寫什麼 1=1 的,上面我只是為了方便演示偷了個懶才那麼寫
正常的話,應該是要將 1=1去掉的:
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog where
<if test="title != null">
and title = #{title};
</if>
<if test="author != null">
and author = #{author};
</if>
</select>
寫成這樣的話,if 標籤再拼接就有問題了,我們配置一下日誌實現,方便檢視拼接的sql
<settings>
<!--方便起見,我們就不配log4j了-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
現在我們再執行一下測試類
在 where後面拼接了一個and,直接導致sql語句錯誤。
實際上還不怎麼好徹底解決這個問題,畢竟不是每次都使用一個條件。and自然是必不可少的。
MyBatis 有一個簡單且適合大多數場景的解決辦法。而在其他場景中,可以對其進行自定義以符合需求。而這,只需要一處簡單的改動。
where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除。
也就是說,我們只需要用一個where標籤將 if標籤包裹,就能夠避免在 where後拼接 and的問題,最重要的是不需要對我們的拼接sql進行任何改動。
<mapper namespace="com.molu.mapper.BlogMapper">
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog
<where >
<if test="title != null">
and title = #{title};
</if>
<if test="author != null">
and author = #{author};
</if>
</where>
</select>
</mapper>
再次執行測試類
可以看到,我們拼接sql開頭的and,在執行後直接就被去掉了,從根本上避免了上面的問題。
如果我們不追加條件where是會被自動去掉的,也就實現了 不追加條件則返回全部部落格的功能
如果 where 元素與你期望的不太一樣,你也可以通過自定義 trim 元素來定製 where 元素的功能。比如,和 where 元素等價的自定義 trim 元素為:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
如果where標籤沒辦法解決你的問題,你還可以使用 trim標籤來改變 where標籤的規則,進行一些簡單的自定義操作。這個就不再展開了,感興趣可以嘗試一下。
choose、when、otherwise
有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。
還是上面的例子,但是策略變為:傳入了 “title” 就按 “title” 查詢,傳入了 “author” 就按 “author” 查詢的情形。若兩者都沒有傳入,就返回瀏覽量為 9999 的 Blog(這可能是管理員認為,與其返回大量的無意義隨機 Blog,還不如返回一些由管理員精選的 Blog)。
我們來實現一下,首先修改mapper.xml
<mapper namespace="com.molu.mapper.BlogMapper">
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
views = 9999
</otherwise>
</choose>
</where>
</select>
</mapper>
寫成這樣後,就不存在 不追加條件返回全部部落格了。它至少會滿足一個條件的,即便我們什麼條件都不追加,他也會將 otherwise
標籤中的views = 9999
拼接上,也就實現了與其返回大量的無意義隨機 Blog,還不如返回一些由管理員精選的 Blog
。
嘗試不追加條件
嘗試將條件全部追加
條件全部追加它只會執行最初滿足的條件,類似Java 中的 switch 語句。所以就不會存在什麼條件互斥的問題,它只會滿足一個條件,也就是第一個滿足的條件。
set
set 類似於where,它也是用來避免我們拼接sql時出錯的
set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號。因為我們進行sql拼接時,很難避免這個 “ , ” 的問題。
我們寫一個修改操作
- 首先在mapper介面中寫對應的方法
public interface BlogMapper {
// 根據id修改部落格
int updateBlog(Map map);
}
- 修改 mapper.xml檔案
<mapper namespace="com.molu.mapper.BlogMapper">
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
where id = #{id};
</update>
</mapper>
看到這樣一個拼接sql,我們很明顯能夠感覺到會出錯,逗號明顯多了一個,最後它會拼接成:
update mybatis.bolg set title = #{title},author = #{author}, where id = #{id};
這樣的sql按道理是沒辦法執行的,但如果是在set標籤中,那就無所謂了。
- 測試類
@Test
public void updateBlogTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title","Web入門");
map.put("author","lin");
map.put("id","7280ac58a49040818f97cf3d59e598dd");
mapper.updateBlog(map);
sqlSession.commit();
sqlSession.close();
}
- 測試 結果:
set 和 where一樣,你也可以使用 trim標籤來改變 set標籤的規則,進行一些簡單的自定義操作。
SQL片段
我們在演示上面這些標籤的時候,有一些sql語句頻繁被用到,我們可以使用 sql標籤將他抽取出來。實現 sql的複用。
比如這幾行sql,基本上一直在用。
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
author = #{author}
</if>
我們使用 sql標籤將他提取出來,取一個比較符合的id。
<sql id="TA_sql">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
author != #{author}
</if>
</sql>
之後我們需要用到這段 sql的時候,可以使用 include
標籤將其引用過來。
<select id="queryBlog" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<include refid="TA_sql"></include>
</where>
</select>
測試類
@Test
public void queryBlogTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("title","Web入門");
List<Blog> blogs = mapper.queryBlog(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
測試結果:
可以看到,通過這種引用sql片段的方式,也能夠達我們想要的效果 且更省事。
foreach
foreach 元素的功能非常強大,它允許你指定一個集合,宣告可以在元素體內使用的集合項(item)和索引(index)變數。它也允許你指定開頭與結尾的字串以及集合項迭代之間的分隔符。這個元素也不會錯誤地新增多餘的分隔符,看它多智慧!提示 你可以將任何可迭代物件(如 List、Set 等)、Map 物件或者陣列物件作為集合引數傳遞給 foreach。當使用可迭代物件或者陣列時,index 是當前迭代的序號,item 的值是本次迭代獲取到的元素。當使用 Map 物件(或者 Map.Entry 物件的集合)時,index 是鍵,item 是值。
看這些比較官方的文字還是看不出個所以然,我們仍然通過案例來理解。
既然要遍歷集合,那我們的UUID就不適用了,修改成1 2 3 4吧
- 在 mapper介面中寫我們對應的方法
public interface BlogMapper {
List<Blog> queryBlogForeach(Map map);
}
- 編寫mapper.xml檔案
<mapper namespace="com.molu.mapper.BlogMapper">
<select id="queryBlogForeach" resultType="blog" parameterType="map">
select * from mybatis.blog
<where>
<!--使用了 foreach標籤-->
<foreach collection="Fids" open="(" separator="or" close=")" item="Fid">
id = #{Fid}
</foreach>
</where>
</select>
</mapper>
- 測試類
@Test
public void queryBlogTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList Fids = new ArrayList();
Fids.add(1);
Fids.add(2);
map.put("Fids",Fids);
mapper.queryBlogForeach(map);
sqlSession.close();
}
- 測試結果:
到這裡大致應該能猜 foreach標籤到底做了什麼
其實 foreach
標籤做的事情很簡單,無非也是拼接sql,只是在拼接前 執行了一個遍歷集合的操作。
<!--使用了 foreach標籤-->
<foreach collection="Fids" item="Fid" open="(" separator="or" close=")" >
id = #{Fid}
</foreach>
如果我們要實現通過傳入的 id來查詢指定的部落格功能,sql語句應該寫成這樣:
select * from mybatis.blog where 1=1 and(id = #{id} or id = #{id} or .....)
這樣寫的話,怎麼看怎麼不舒服。一來是寫死了,有侷限性 ,二來這樣寫sql 不免讓人覺得有點low…
而我們通過 foreach
標籤的話可以做到動態的傳入指定的 id。
從 foreach
的屬性著手
collection
,表示要遍歷的集合名字,也就是我們要在測試類中要建立的 listitem
,表示遍歷出來的每一個元素的名字,也就是我們要寫在下面條件中的 引數(Fid)open
,表示拼接 sql的起始separator
,表示拼接 sql時使用的分隔符close
,表示拼接 sql的結尾
看完這些你應該對foreach標籤有了一定的瞭解。
也就是說,我們通過map傳入的list集合,會被 foreach
標籤進行遍歷
遍歷出來的每一個元素 都會被作為條件傳入到我們的 where子句後面。
由於我們使用了 where
標籤,所以哪怕我們的集合中什麼都沒有,sql也是能夠執行成功的。
如果集合中有元素,該元素就會被foreach
標籤拿去拼接。
我們又對open、separator close、進行了定義,那麼拼接進去的sql 也就不會出問題了。
每個傳入的元素之間會使用 or 進行分隔,起始和結尾是一對括號,也就實現了 ( id = #{Fid} or id = #{iFd} or …)的拼接。
所謂的的動態SQL本質還是SQL語句,只是我們可以在SQL層面,執行一些邏輯程式碼
動態SQL就是在拼接SQL語句,我們只需要保證SQL的正確性,按照SQL的格式想辦法對其進行排列組合就可以了
放鬆一下眼睛
原圖地址
畫師主頁
相關文章
- mybatis動態sql總結MyBatisSQL
- MyBatis 學習總結MyBatis
- Mybatis學習總結MyBatis
- MyBatis 動態 SQLMyBatisSQL
- mybatis動態SQLMyBatisSQL
- MyBatis(七) 動態SQLMyBatisSQL
- 1 Mybatis動態SQLMyBatisSQL
- Mybatis--動態SQLMyBatisSQL
- MyBatis學習總結(24)——Mybatis常見問題彙總MyBatis
- Mybatis-06 動態SqlMyBatisSQL
- sql語句學習總結SQL
- 學習MyBatis必知必會(7)~註解開發、動態SQLMyBatisSQL
- MyBatis對動態SQL的支援MyBatisSQL
- mybatis動態sql與分頁MyBatisSQL
- Java-Mybatis動態SQL整理JavaMyBatisSQL
- Mybatis介紹之 動態SQLMyBatisSQL
- MyBatis框架之SQL對映和動態SQLMyBatis框架SQL
- Mybatis 動態執行SQL語句MyBatisSQL
- 學習Java的第五週總結Java
- SpringCloud Alibaba Nacos 配置動態更新原始碼學習總結SpringGCCloud原始碼
- Mybatis where 1=1 動態sql問題MyBatisSQL
- MyBatis 動態 SQL 最全教程,這樣寫 SQL 太優雅了!MyBatisSQL
- MyBatis標籤實現的動態SQL語句MyBatisSQL
- Mybatis(一)Porxy動態代理和sql解析替換MyBatisSQL
- MyBatis系列(七):MyBatis動態Sql之choose,where,set標籤的用法MyBatisSQL
- 學習總結
- 動態規劃 總結動態規劃
- MyBatis從入門到精通(六):MyBatis動態Sql之if標籤的用法MyBatisSQL
- 資料結構 其五 樹與二叉樹學習總結資料結構二叉樹
- MyBatis從入門到精通(八):MyBatis動態Sql之foreach標籤的用法MyBatisSQL
- mybatis原始碼學習------resultMap和sql片段的解析MyBatis原始碼SQL
- ConstraintLayout 學習總結AI
- BOM學習總結
- tkinter學習總結
- vue學習總結Vue
- HSF學習總結
- ElasticSearch 學習總結Elasticsearch
- Storm學習總結ORM