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 的強大特性之一。如果你使用過 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語句,這樣做很麻煩。
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;
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>