Mybatis多表查詢與動(dòng)態(tài)SQL特性詳解
1.較復(fù)雜的查詢操作
1.1 參數(shù)占位符 #{} 和 ${}
#{}:預(yù)處理符,如將id=#{2}替換為id=?,然后使用2替換?。
${}:替換符,如將id=${2}替換為id=2。
兩種占位符都可以正常使用的場(chǎng)合:傳入的參數(shù)類型是數(shù)值類型
使用${}:
select * from userinfo where id=${id}
select * from userinfo where id=2
使用#{}:
select * from userinfo where id=#{id}
select * from userinfo where id=?
對(duì)于這兩種參數(shù)占位符,個(gè)人建議能使用#{}就使用#{},因?yàn)?code>${}存在SQL注入的問(wèn)題,以及如果傳入的類型是字符串也會(huì)出類型。
只能使用#{}而不能使用${}的場(chǎng)合:傳入?yún)?shù)類型為String
使用${}:
select * from userinfo where username=${username}
//實(shí)際執(zhí)行的語(yǔ)句
Preparing: select * from userinfo where username=張三
但是在sql中通過(guò)字符串來(lái)查詢數(shù)據(jù),是需要加上引號(hào)的,而使用${}生成的sql并沒(méi)有帶引號(hào),因此不適用于字符串參數(shù)的sql。
使用#{}:
select * from userinfo where username=#{username}
//實(shí)際執(zhí)行語(yǔ)句
select * from userinfo where username=?
由于使用#{}是將目標(biāo)參數(shù)替換為占位符,然后利用JDBC中的占位符機(jī)制實(shí)現(xiàn)sql語(yǔ)句的填充,所以使用#{}構(gòu)造的sql是可以正常運(yùn)行的,并且沒(méi)有SQL注入的問(wèn)題。
所以,#{}相比于${},它支持所有類型的參數(shù),包括數(shù)值類與字符串類,而${}支持?jǐn)?shù)值類型,不支持字符串類型的參數(shù),但也可以在原來(lái)sql里面為${}外面加上一對(duì)引號(hào)。
select * from userinfo where username='${username}'
//實(shí)際執(zhí)行語(yǔ)句
Preparing: select * from userinfo where username='張三'
當(dāng)傳遞的參數(shù)為字符串類型的時(shí)候,雖然加上一對(duì)引號(hào),使用${}也可以做到,但是${}存在SQL注入問(wèn)題,所以仍然不推薦,有關(guān)SQL注入后面我們會(huì)介紹到。
大部分場(chǎng)合下,使用#{}都可以解決,但還是存在一小部分只能是${}來(lái)處理的。
如當(dāng)我們需要按照升序或者逆序得到數(shù)據(jù)庫(kù)查詢結(jié)果的時(shí)候,這種場(chǎng)合就只能使用${},使用#{}會(huì)報(bào)錯(cuò),我們來(lái)演示一下。
首先,我們?cè)?code>Mapper接口中聲明一個(gè)方法:作用就是按照排序獲取結(jié)果集
public List<UserInfo> getOrderList(@Param(value = "order") String order);
我們?cè)偃?code>xml文件中去寫sql語(yǔ)句:首先我們使用$進(jìn)行演示
<select id="getOrderList" resultType="com.example.demo.model.UserInfo">
select * from userinfo order by createtime ${order};
</select>
我們進(jìn)行一個(gè)單元測(cè)試,單元測(cè)試代碼很簡(jiǎn)單,就是調(diào)用sql,然后得到結(jié)果集:
@Test
void getOrderList() {
List<UserInfo> userMappers = userMapper.getOrderList("desc");
System.out.println(userMappers);
}
單元測(cè)試結(jié)果:
可以正常查詢,得到的結(jié)果與預(yù)期也是相同的。

我們?cè)賮?lái)試一試使用#{}來(lái)構(gòu)造sql語(yǔ)句:
<select id="getOrderList" resultType="com.example.demo.model.UserInfo">
select * from userinfo order by createtime #{order};
</select>
單元測(cè)試邏輯與代碼不變,我們?cè)賮?lái)看看單元測(cè)試執(zhí)行的一個(gè)結(jié)果:

我們發(fā)現(xiàn)程序報(bào)錯(cuò)了,這是因?yàn)槭褂昧?code>desc的字符串替換了占位符,而我們所需要的不是一個(gè)desc字符串,而是直接一個(gè)desc的關(guān)鍵字,所以sql也拋出了語(yǔ)法錯(cuò)誤,最終執(zhí)行的sql為:
select * from userinfo order by createtime ‘desc';
期望執(zhí)行的sql為:
select * from userinfo order by createtime desc;
所以在傳遞sql關(guān)鍵字的時(shí)候,不能使用#{},只能使用${}。

1.2SQL注入
SQL注入就是使用${},使用一些特殊的語(yǔ)句,來(lái)達(dá)到非法獲取數(shù)據(jù)的目的,如不通過(guò)正確的密碼獲取某賬戶的信息,下面我們來(lái)演示一下,就以登錄的例子來(lái)演示,SQL注入可以在不知道密碼的前提下登錄成功,并且獲取到用戶的相關(guān)信息。
首先我將數(shù)據(jù)庫(kù)只保留一個(gè)用戶信息,目的是為了方便演示SQL注入問(wèn)題:

第一步,在Mapper接口中定義方法login,返回登錄成功的用戶對(duì)象。
public UserInfo login(@Param("username") String username, @Param(("password")) String password);
第二步,在xml文件中編寫SQL語(yǔ)句,我們需要演示SQL注入,所以我們使用${}來(lái)構(gòu)造sql語(yǔ)句。
<select id="login" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username='${username}' and password='${password}';
</select>
第三步,編寫測(cè)試類,我們?cè)谶@個(gè)測(cè)試類中,傳入有注入問(wèn)題的SQL語(yǔ)句' or 1='1,使得不需要密碼就能拿到相關(guān)的用戶信息。
@Test
void login() {
String username = "admin";
String password = "' or 1='1";
UserInfo userInfo = userMapper.login(username, password);
System.out.println(userInfo);
}
單元測(cè)試運(yùn)行結(jié)果:
我們?cè)诓恢烙脩裘艽a的情況下,登錄成功,并拿到了用戶的信息。

最終執(zhí)行的一段sql語(yǔ)句為:
select * from userinfo where username='admin' and password='' or 1='1';
相當(dāng)于它在原來(lái)?xiàng)l件判斷的語(yǔ)句下,后面有加上一個(gè)或的邏輯,并且或后面的表達(dá)式為true,這樣就使得原來(lái)的SQL語(yǔ)句中的條件判斷部分一定為真,所以就在密碼不知道的情況下拿到了用戶的基本信息。
所以我們能不使用#{}就不使用${},因?yàn)榇嬖赟QL注入問(wèn)題,如果必須使用${}則需要驗(yàn)證一下傳遞的參數(shù)是否合法,比如上面定義排序的sql,傳遞的參數(shù)只能是desc或者是asc,如果不是就不能執(zhí)行這條SQL,防止SQL注入的發(fā)生。
1.3like查詢
在Mybatis中使用like查詢比較特殊,因?yàn)橹苯邮褂?code>#{}會(huì)報(bào)錯(cuò),而使用${},由于輸入的字符串情況很多,無(wú)法做到枚舉,驗(yàn)證比較困難,無(wú)法避免SQL注入問(wèn)題。
首先,我們來(lái)演示使用#{}進(jìn)行like查詢,步驟我就不詳細(xì)寫了,就是查詢的步驟。
第一步,聲明方法。
public List<UserInfo> getListByName(@Param("username") String username);
第二步,xml寫sql。
<select id="getListByName" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username like '%#{username}%'
</select>
第三步,單元測(cè)試。
@Test
void getListByName() {
String username = "a";
List<UserInfo> list = userMapper.getListByName(username);
for (UserInfo userInfo : list) {
System.out.println(userInfo);
}
}
運(yùn)行結(jié)果:報(bào)錯(cuò)了,因?yàn)?code>#{}會(huì)被替換成一個(gè)字符串,而在這個(gè)%#{username}%語(yǔ)句中#{username}不能帶上引號(hào),帶上就違背SQL語(yǔ)法,造成錯(cuò)誤。

這個(gè)時(shí)候,由于#{}多出一對(duì)引號(hào),${}無(wú)法枚舉所有情況進(jìn)行驗(yàn)證會(huì)產(chǎn)生SQL注入,所以不能直接使用#{},我們需要搭配MySQL內(nèi)置的字符串拼接語(yǔ)句concat。
我們將sql改為concat進(jìn)行字符串拼接:
<select id="getListByName" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username like concat('%', #{username}, '%')
</select>
重新測(cè)試一下,發(fā)現(xiàn)可以正常執(zhí)行了:

1.4resultType與resultMap
resultType表示數(shù)據(jù)庫(kù)返回的數(shù)據(jù)映射在java程序中所對(duì)應(yīng)的類型,只要定義類中的字段名與數(shù)據(jù)庫(kù)中表的字段名字一致就沒(méi)有任何問(wèn)題,但是如果字段名存在沖突,則沖突的字段無(wú)法獲取到數(shù)據(jù)庫(kù)查詢的結(jié)果。
比如用戶名屬性在數(shù)據(jù)庫(kù)中的名字是username,而在java程序類中的屬性名為name,此時(shí)通過(guò)mybatis將數(shù)據(jù)傳遞到程序中的對(duì)象時(shí),獲取到的name屬性為null,就不能正確地獲取到對(duì)應(yīng)的屬性值,為了解決這個(gè)數(shù)據(jù)庫(kù)字段與類中中字段不匹配的問(wèn)題,我們需要使用到resultMap。
resultMap的使用方式就是在xml文件中設(shè)置<resultMap>標(biāo)簽,至少需要設(shè)置兩個(gè)屬性,一個(gè)是id表示你這個(gè)resultMap標(biāo)簽的名字,還有一個(gè)是type屬性,它表示映射到程序中類的類型,需包含包名。
這個(gè)標(biāo)簽里面需要設(shè)置至少兩個(gè)子標(biāo)簽,一個(gè)是id標(biāo)簽,另外一個(gè)是result標(biāo)簽,前者表示主鍵,后者表示數(shù)據(jù)庫(kù)表中普通的列,這兩種標(biāo)簽也是至少需要設(shè)置兩個(gè)屬性,一個(gè)是column表示數(shù)據(jù)庫(kù)表中的字段名,另外一個(gè)是property表示程序類中的字段名,如果只是在單表進(jìn)行查詢,只設(shè)置不同字段名的映射就可以了,但是如果是多表查詢,必須將數(shù)據(jù)表中所有的字段與類中所有的字段生成映射關(guān)系。
就像下面這樣,圖中類與數(shù)據(jù)表字段是相同的,實(shí)際情況會(huì)存在不同的字段名:

1.4多表查詢
1.4.1一對(duì)一表映射
一對(duì)一關(guān)系就是對(duì)于一個(gè)屬性只與另外一個(gè)屬性有關(guān)系的映射,這就是一對(duì)一的關(guān)系,舉個(gè)例子,對(duì)于一篇博客,它只會(huì)對(duì)應(yīng)到一個(gè)用戶,則博客與用戶的關(guān)系是一對(duì)一的關(guān)系,下面我們嘗試在mybatis中實(shí)現(xiàn)一對(duì)一多表聯(lián)查。
那么,首先我們先將數(shù)據(jù)庫(kù)的博客表與查程序中的博客類對(duì)應(yīng)起來(lái),就是按照數(shù)據(jù)庫(kù)中的博客表建立一個(gè)類:

@Data
public class Articleinfo {
private Integer id;
private String title;
private String content;
private String createtime;
private String updatetime;
private Integer uid;
private Integer rcount;
private Integer state;
//不妨多一個(gè)屬性,用戶表
private UserInfo userInfo;
}
目前文章表中只有一條數(shù)據(jù),如下圖:

第二步,創(chuàng)建Mapper接口和對(duì)應(yīng)的xml文件。
第三步,在接口中聲明方法和在xml中寫sql標(biāo)簽與語(yǔ)句。
//根據(jù)文章名稱獲取文章對(duì)象
public Articleinfo getArticleById(@Param("id") Integer id);
<select id="getArticleById" resultType="com.example.demo.model.Articleinfo">
select * from articleinfo where id=#{id};
</select>
第四步,編寫測(cè)試方法,我們直接調(diào)用查詢方法,然后使用日志輸出對(duì)象。
@Test
void getArticleById() {
Articleinfo articleinfo = articleMapper.getArticleById(1);
log.info("文章詳情:" + articleinfo);
}
由于我們數(shù)據(jù)表與類的字段名是一致的,那些普通的屬性都一一對(duì)應(yīng)上了,都成功被賦值了,但是由于UserInfo類在數(shù)據(jù)表中沒(méi)有,所以并沒(méi)有得到UserInfo對(duì)象,如果我們想要拿到這個(gè)對(duì)象,我們得使用resultMap。

問(wèn)題主要有兩個(gè),第一,數(shù)據(jù)庫(kù)查詢到的用戶表沒(méi)有映射到UserInfo對(duì)象,第二,查詢的SQL語(yǔ)句是單表查詢語(yǔ)句,不是多表查詢語(yǔ)句。
所以想要實(shí)現(xiàn)一對(duì)一多表查詢,需要設(shè)置多表查詢SQL語(yǔ)句,我們使用左外連接進(jìn)行多表查詢:
<select id="getArticleById" resultMap="BaseMap">
select a.*, u.* from articleinfo as a left join userinfo as u on a.uid=u.id where a.id=#{id};
</select>
此外,我們除了設(shè)置UserInfo與Articleinfo類中每個(gè)屬性與數(shù)據(jù)表的映射之外,我們還要在Articleinfo類對(duì)應(yīng)的resultMap中使用association標(biāo)簽。最少需要設(shè)置兩個(gè)屬性,一個(gè)是property表示在主表Articleinfo中對(duì)應(yīng)副表UserInfo映射對(duì)象的變量名,另外一個(gè)是副表UserInfo對(duì)應(yīng)的resultMap。
Articleinfo類對(duì)應(yīng)的resultMap:
<resultMap id="BaseMap" type="com.example.demo.model.Articleinfo">
<id column="id" property="id"></id>
<result column="title" property="title"></result>
<result column="content" property="content"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="uid" property="uid"></result>
<result column="rcount" property="rcount"></result>
<result column="state" property="state"></result>
<association property="userInfo" resultMap="com.example.demo.mapper.UserMapper.BaseMap"></association>
</resultMap>
UserInfo類對(duì)應(yīng)的resultMap:
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
<!-- column 表示數(shù)據(jù)庫(kù)字段名 property 表示對(duì)應(yīng)對(duì)象的字段名,設(shè)置這兩個(gè)值可以建立映射-->
<!-- 主鍵約束-->
<id column="id" property="id"></id>
<!--普通變量映射-->
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="photo" property="photo"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="state" property="state"></result>
</resultMap>
如果UserInfo類的resultMap沒(méi)有將所有的屬性都與數(shù)據(jù)庫(kù)的表映射,就會(huì)造成獲取到的userInfo對(duì)象中的數(shù)據(jù)不完整,假設(shè)只設(shè)置了id與name的映射,那獲取到的對(duì)象只有id與name有值。
將兩張表的resultMap映射好后,我們運(yùn)行同樣的單元測(cè)試案例,運(yùn)行結(jié)果如下:

但是,仍然存在一個(gè)問(wèn)題,那就是我們所建的兩個(gè)表存在名字相同的字段,可能會(huì)出現(xiàn)數(shù)據(jù)覆蓋的情況,如兩個(gè)表的主鍵都叫id,但是id在兩個(gè)表的含義是不同的,在用戶表它表示用戶id,在文章表它表示文章的id,現(xiàn)在我們將獲取兩表的數(shù)據(jù)的id改為不相同,再來(lái)看一看單元測(cè)試運(yùn)行的結(jié)果:


按理來(lái)說(shuō),由于不存在id為1的用戶,所以獲取到UserInfo對(duì)象應(yīng)該為null才對(duì),但是運(yùn)行的結(jié)果卻存在UserInfo對(duì)象,并且與文章表的重名字段都被賦值了文章表中的數(shù)據(jù),為了解決這個(gè)問(wèn)題,我們必須在文章表(主表)的resultMap中設(shè)置屬性columnPrefix,它的值隨便設(shè)置,作用是識(shí)別副表字段時(shí)加上一段前綴,如我們給用戶表的字段加上前綴u_,此時(shí)sql中就不能使用*來(lái)一次表示所有元素了,需要一個(gè)一個(gè)單獨(dú)設(shè)置,并將字段全部重命名,帶上u_前綴 。
association字段設(shè)置:
<association property="userInfo" columnPrefix="u_" resultMap="com.example.demo.mapper.UserMapper.BaseMap" ></association>
SQL語(yǔ)句需要將用戶表的字段全部重命名:
<select id="getArticleById" resultMap="BaseMap">
select a.*, u.id as u_id,
u.username as u_username,
u.password as u_password,
u.photo as u_photo,
u.createtime as u_createtime,
u.updatetime as u_updatetime,
u.state as u_state
from articleinfo as a left join userinfo as u on a.uid=u.id where a.id=#{id};
</select>

我們將userInfo對(duì)應(yīng)的用戶表的id再改回為1,讓查詢有關(guān)于UserInfo類的數(shù)據(jù)。

再次運(yùn)行單元測(cè)試案例:

我們是能夠獲取到相應(yīng)的數(shù)據(jù)的,所以如果兩個(gè)表字段重名了,進(jìn)行多表查詢時(shí),需要設(shè)置columnPrefix屬性,這樣才能夠避免不同表同名字段數(shù)據(jù)覆蓋的問(wèn)題。
所以,在創(chuàng)建數(shù)據(jù)庫(kù)的數(shù)據(jù)表時(shí),盡量不要讓表與表中的字段重名。
1.4.2一對(duì)多表映射
一對(duì)多的關(guān)系,就是對(duì)于一個(gè)屬性,它對(duì)映著多個(gè)其他的屬性,比如用戶與博客之間的關(guān)系,一個(gè)用戶可以對(duì)應(yīng)多篇博客,則用戶與博客之間的關(guān)系就是一對(duì)多的關(guān)系。同樣的下面我們嘗試使用mybatis實(shí)現(xiàn)多對(duì)多的多表聯(lián)查。
下面我們以用戶表為主,文章表為輔,來(lái)演示如何進(jìn)行一對(duì)多關(guān)系的多表查詢。
既然是一對(duì)多的關(guān)系,那我們可以在UserInfo類中加上一個(gè)儲(chǔ)存ArticleInfo對(duì)象的List,來(lái)儲(chǔ)存用戶發(fā)布或所寫的文章。
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private String photo;
private String createtime;
private String updatetime;
private Integer state;
private List<Articleinfo> aList;
}
實(shí)現(xiàn)多表查詢的大致過(guò)程如下:
- 在Mapper接口中聲明方法,我們聲明一個(gè)方法,就是通過(guò)用戶id獲取用戶信息以及對(duì)應(yīng)的文章列表。
- 在
xml文件當(dāng)中寫resultMap映射關(guān)系,與一對(duì)一多表查詢不同的是,我們需要設(shè)置collection標(biāo)簽,而不是association標(biāo)簽。 - 在
xml文件的resultMap標(biāo)簽中至少設(shè)置resultMap名字id,對(duì)應(yīng)映射的類type等屬性,里面需要設(shè)置數(shù)據(jù)表與類中所有字段的映射,以及設(shè)置collection標(biāo)簽,需要設(shè)置property屬性表示需映射的對(duì)象名,設(shè)置resultMap即副表的resultMap路徑,由于你無(wú)法保證表與表之間是否存在重名字段,需要設(shè)置columnPrefix為副表的字段添加上一個(gè)前綴,防止重名數(shù)據(jù)覆蓋。
<collection
property="aList"
resultMap="com.example.demo.mapper.ArticleMapper.BaseMap"
columnPrefix="a_">
</collection>
在對(duì)應(yīng)的xml文件當(dāng)中寫SQL標(biāo)簽以及語(yǔ)句。
<select id="getUserAndArticlesById" resultMap="BaseMap">
select u.*,
a.id as a_id,
a.title as a_title,
a.content as a_content,
a.createtime as a_createtime,
a.updatetime as a_updatetime,
a.uid as a_uid,
a.rcount as a_rcount,
a.state as a_state
from userinfo as u left join articleinfo as a on u.id=a.uid where u.id=#{uid}
</select>
編寫單元測(cè)試代碼,測(cè)試代碼是否編寫正確。
@Test
void getUserAndArticlesById() {
Integer id = 1;
UserInfo userInfo = userMapper.getUserAndArticlesById(id);
log.info("用戶信息:" + userInfo);
}
運(yùn)行結(jié)果:

2.動(dòng)態(tài)SQL
首先來(lái)說(shuō)一下什么是動(dòng)態(tài)SQL,官方文檔對(duì)于動(dòng)態(tài)SQL的定義是:
動(dòng)態(tài) SQL 是 MyBatis 的強(qiáng)大特性之一。如果你使用過(guò) JDBC 或其它類似的框架,你應(yīng)該能理解根據(jù)不同條件拼接 SQL 語(yǔ)句有多痛苦,例如拼接時(shí)要確保不能忘記添加必要的空格,還要注意去掉列表最后一個(gè)列名的逗號(hào)。利用動(dòng)態(tài) SQL,可以徹底擺脫這種痛苦。使用動(dòng)態(tài) SQL 并非一件易事,但借助可用于任何 SQL 映射語(yǔ)句中的強(qiáng)大的動(dòng)態(tài) SQL 語(yǔ)言,MyBatis 顯著地提升了這一特性的易用性。
前面所說(shuō)的mybatis增刪查改,那些傳入的參數(shù)都是一定會(huì)傳入的,但是在實(shí)際情況中,很多參數(shù)都是非必傳參數(shù),使用動(dòng)態(tài)SQL就可以解決傳入的參數(shù)是非必傳參數(shù)的情況。
動(dòng)態(tài)SQL可以解決多余符號(hào)的問(wèn)題,如,等。
2.1if標(biāo)簽
if標(biāo)簽的作用就是判斷一個(gè)參數(shù)是否有值,如果沒(méi)有值就將對(duì)應(yīng)的參數(shù)隱藏。
語(yǔ)法:
<if test="表達(dá)式"> sql </if> //例如 <if test="參數(shù)!=null"> sql部分語(yǔ)句 </if>
當(dāng)表達(dá)式為真,則插入if標(biāo)簽中的sql,否則不插入。
我們以在用戶表中插入一條數(shù)據(jù)為例,插入的數(shù)據(jù)中頭像photo不是必傳的參數(shù):
方法聲明:
//使用動(dòng)態(tài)sql插入數(shù)據(jù)
public int addUser(UserInfo userInfo);
動(dòng)態(tài)SQL語(yǔ)句:
其中的photo是非必傳參數(shù),我們使用if標(biāo)簽來(lái)判斷它是否有值,沒(méi)有值就不插入目標(biāo)的SQL語(yǔ)句。
<insert id="addUser">
insert into userinfo(username, password
<if test="photo!=null">
, photo
</if>
) values(#{username}, #{password}
<if test="photo!=null">
, #{photo}
</if>
)
</insert>
單元測(cè)試代碼:
@Test
void addUser() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("張三瘋");
userInfo.setPassword("123456");
int res = userMapper.addUser(userInfo);
log.info("受影響的行數(shù)為:" + res);
}
在單元測(cè)試代碼中,我沒(méi)有給photo賦值,if標(biāo)簽會(huì)判斷它為空,不會(huì)插入對(duì)應(yīng)photo的SQL,因此插入數(shù)據(jù)photo為默認(rèn)值。
結(jié)果:

數(shù)據(jù)庫(kù)查詢結(jié)果:

再來(lái)試一試給photo傳值的情況,它生成的SQL有三個(gè)參數(shù):
@Test
void addUser() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("張無(wú)忌");
userInfo.setPassword("12345611");
userInfo.setPhoto("張無(wú)忌.png");
int res = userMapper.addUser(userInfo);
log.info("受影響的行數(shù)為:" + res);
}
運(yùn)行結(jié)果:
最終生成的語(yǔ)句多了一個(gè)photo參數(shù)。

2.2trim標(biāo)簽
前面所說(shuō)的if標(biāo)簽可以實(shí)現(xiàn)非必傳參數(shù)SQL的構(gòu)造,在極端情況下,有很多個(gè)非必傳參數(shù),此時(shí)如果只使用if標(biāo)簽構(gòu)造出的SQL語(yǔ)句很有可能會(huì)多出一個(gè),,因?yàn)橛泻芏喾潜貍鲄?shù),如果只傳來(lái)一個(gè)參數(shù),由于不確定后面是否還會(huì)有參數(shù),因此會(huì)預(yù)留一個(gè),,此時(shí)如果沒(méi)有其他參數(shù),就會(huì)多出一個(gè)參數(shù)。
而trim標(biāo)簽可以做到這一點(diǎn),它可以去除SQL語(yǔ)句前后多余的某個(gè)字符,它需要搭配if標(biāo)簽使用。
<trim>標(biāo)簽中有如下屬性:
- prefix:表示整個(gè)語(yǔ)句塊,以prefix的值作為前綴
- suffix:表示整個(gè)語(yǔ)句塊,以suffix的值作為后綴
- prefixOverrides:表示整個(gè)語(yǔ)句塊要去除掉的前綴
- suffixOverrides:表示整個(gè)語(yǔ)句塊要去除掉的后綴
語(yǔ)法:
<trim prefix="前綴符", suffix="后綴符", prefixOverrides="去除多余的前綴字符", suffixOverrides="去除多余的后綴字符"> <if test="表達(dá)式"> ... </if> ... ... </trim>
假設(shè)username password photo都是非必傳參數(shù),但是至少傳遞一個(gè),我們來(lái)寫插入語(yǔ)句的動(dòng)態(tài)SQL。
<insert id="addUser2">
insert into userinfo
<trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=",">
<if test="username!=null">
username,
</if>
<if test="password!=null">
password,
</if>
<if test="photo!=null">
photo
</if>
</trim>
values
<trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=",">
<if test="username!=null">
#{username},
</if>
<if test="password!=null">
#{password},
</if>
<if test="photo!=null">
#{photo}
</if>
</trim>
</insert>
單元測(cè)試代碼:
@Test
void addUser2() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wangwu");
userInfo.setPassword("12345622");
int res = userMapper.addUser(userInfo);
log.info("受影響的行數(shù)為:" + res);
}
運(yùn)行結(jié)果與生成的SQL語(yǔ)句:

我們發(fā)現(xiàn)逗號(hào)它自動(dòng)去除了。
2.3where標(biāo)簽
where標(biāo)簽主要是實(shí)現(xiàn)where關(guān)鍵字的替換,如果SQL中沒(méi)有使用到where(沒(méi)有查詢條件),就會(huì)隱藏,存在查詢條件,就會(huì)生成含有where的查詢SQL語(yǔ)句,并且可以去除前面的and。
我們就不以復(fù)雜的案例作為演示了,直接寫一個(gè)最簡(jiǎn)單的查詢,為了簡(jiǎn)單,我們刪除數(shù)據(jù)庫(kù)的其他數(shù)據(jù),只留admin一條數(shù)據(jù)。

下面我們來(lái)寫最簡(jiǎn)單的查詢語(yǔ)句,就是根據(jù)id獲取一個(gè)用戶信息。
寫動(dòng)態(tài)SQL時(shí),我故意在最前面寫一個(gè)and來(lái)證明where標(biāo)簽是可以自動(dòng)刪除前面多余的and。
<select id="getUserById" resultType="com.example.demo.model.UserInfo">
select * from userinfo
<where>
<if test="id!=null">
and id=#{id}
</if>
</where>
</select>
單元測(cè)試代碼:
@Test
void gerUserById() {
UserInfo userInfo = userMapper.getUserById(1);
System.out.println(userInfo);
//Assertions.assertNotNull(userInfo);
}
結(jié)果:
發(fā)現(xiàn)自動(dòng)生成了where語(yǔ)句并刪除了多余的and。

如果我們查詢一個(gè)null值,就不會(huì)生成where語(yǔ)句。

以上<where>標(biāo)簽也可以使用 <trim prefix="where" prefixOverrides="and"> 替換。
2.4set標(biāo)簽
其實(shí)set標(biāo)簽與where標(biāo)簽很相似,只不過(guò)where用來(lái)替換查詢SQL,set用于修改SQL中,用來(lái)自動(dòng)生成set部分的SQL語(yǔ)句。
set標(biāo)簽還可以自動(dòng)去除最后面的一個(gè),。
比如我們寫一個(gè)能夠修改賬戶名username,密碼password,頭像photo的動(dòng)態(tài)SQL,根據(jù)id進(jìn)行修改。
方法聲明:
//使用動(dòng)態(tài)SQL實(shí)現(xiàn)修改用戶信息,包括賬戶名,密碼,頭像
public int updateUser(UserInfo userInfo);
動(dòng)態(tài)SQL:
<update id="updateUser">
update userinfo
<set>
<if test="username!=null">
username=#{username},
</if>
<if test="password!=null">
password=#{password},
</if>
<if test="photo!=null">
photo=#{photo}
</if>
</set>
where id=#{id}
</update>
單元測(cè)試代碼:
@Test
void updateUser() {
UserInfo userInfo = new UserInfo();
//修改密碼為123456
userInfo.setPassword("123456");
userInfo.setId(1);
int res = userMapper.updateUser(userInfo);
log.info("受影響的行數(shù):" + res);
}
運(yùn)行結(jié)果:
修改成功并且可以根據(jù)傳入?yún)?shù)個(gè)數(shù)自動(dòng)生成相應(yīng)的修改SQL,以及可以自動(dòng)去除最后的,。

以上<set>標(biāo)簽也可以使用 <trim prefix="set" suffixOverrides=","> 替換。
2.5foreach標(biāo)簽
對(duì)集合進(jìn)行遍歷可以使用foreach標(biāo)簽,常用的場(chǎng)景有批量刪除功能。
- collection:綁定方法參數(shù)中的集合,如 List,Set,Map或數(shù)組對(duì)象
- item:遍歷時(shí)的每一個(gè)對(duì)象
- open:語(yǔ)句塊開(kāi)頭的字符串
- close:語(yǔ)句塊結(jié)束的字符串
- separator:每次遍歷之間間隔的字符串
為了方便演示批量刪除,我們隨便插入幾條數(shù)據(jù)到數(shù)據(jù)庫(kù):

方法聲明:
//使用動(dòng)態(tài)sql批量刪除元素
public int deleteIds(List<Integer> ids);
動(dòng)態(tài)SQL語(yǔ)句:
<delete id="deleteIds">`在這里插入代碼片`
delete from userinfo where id in
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
單元測(cè)試代碼:
刪除數(shù)據(jù)庫(kù)中id為10 11 12的用戶。
@Test
void deleteIds() {
List<Integer> ids = new ArrayList<>();
ids.add(10);
ids.add(11);
ids.add(12);
int res = userMapper.deleteIds(ids);
log.info("受影響的行數(shù)" + res);
}
運(yùn)行結(jié)果:

成功生成了批量刪除的SQL,這就是foreach標(biāo)簽的作用,它能夠遍歷集合。

總結(jié)
到此這篇關(guān)于Mybatis多表查詢與動(dòng)態(tài)SQL特性的文章就介紹到這了,更多相關(guān)Mybatis多表查詢與動(dòng)態(tài)SQL內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL范圍查詢優(yōu)化的場(chǎng)景實(shí)例詳解
范圍訪問(wèn)方法使用單一索引去檢索表中的數(shù)據(jù)包含一個(gè)或者多個(gè)索引值的行記錄,下面這篇文章主要給大家介紹了關(guān)于MySQL范圍查詢優(yōu)化的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
MySQL的子查詢及相關(guān)優(yōu)化學(xué)習(xí)教程
這篇文章主要介紹了MySQL的子查詢及相關(guān)優(yōu)化學(xué)習(xí)教程,使用子查詢時(shí)需要注意其對(duì)數(shù)據(jù)庫(kù)性能的影響,需要的朋友可以參考下2015-11-11
Mysql8.0壓縮包安裝方法(詳細(xì)教程一步步安裝)
這篇文章主要給大家介紹了關(guān)于Mysql8.0壓縮包安裝方法,文中介紹的非常詳細(xì),Mysql安裝的時(shí)候可以有msi安裝和zip解壓縮兩種安裝方式,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07
MySql 快速插入千萬(wàn)級(jí)大數(shù)據(jù)的方法示例
這篇文章主要介紹了MySql 快速插入千萬(wàn)級(jí)大數(shù)據(jù)的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Mysql添加聯(lián)合唯一索引及相同數(shù)據(jù)插入報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了Mysql添加聯(lián)合唯一索引及相同數(shù)據(jù)插入報(bào)錯(cuò)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09

