MybatisPlus?構(gòu)造器wrapper的使用與原理解析
上一次我們給大家講解了何為MybatisPlus,我們使用它的目的以及它的主要特性。不過(guò),對(duì)于一線開(kāi)發(fā)而言,如何在代碼中使用上它的特性才是重中之重,本期我們就來(lái)好好講一下 MybatisPlus 中的條件構(gòu)造器
一、構(gòu)造器的分類
我們還是使用一張老圖來(lái)說(shuō)明
- 構(gòu)造器都有一個(gè)核心父類=
AbstractWrapper
=,其他的構(gòu)造器都是它的子類,現(xiàn)在兩種分類方式,一種分類是用途; - 即
查詢
或更新
構(gòu)造器,另一種則是按使用方式,即一般
或lambda
構(gòu)造器。 - 兩種分類交錯(cuò),最后我們就看到了四個(gè)構(gòu)造器實(shí)現(xiàn)類。
1. AbstractWrapper 的作用
作為所有條件構(gòu)造器的父類,AbstractWrapper
肩負(fù)著絕大部分的功能,來(lái)幫助我們實(shí)現(xiàn)各類復(fù)雜的SQL. 它實(shí)現(xiàn)了下面幾個(gè)接口:
Compare
:
-定義了一組方法用于比較操作,包括等于(eq)、不等于(ne)、大于(gt)
Nested
:
-定義了一組方法用于構(gòu)建嵌套條件,即在查詢條件中可以使用括號(hào)包裹的子條件
Join
:
-用于實(shí)現(xiàn)表的關(guān)聯(lián)查詢,通過(guò)指定關(guān)聯(lián)條件、連接方式,可以將多個(gè)表的數(shù)據(jù)進(jìn)行關(guān)聯(lián)查詢。
Func
:
-定義了一組方法用于構(gòu)建SQL函數(shù)表達(dá)式,包括COUNT、SUM、AVG、MAX、MIN等函數(shù)
2. 普通構(gòu)造器與lambda構(gòu)造器
我們首先看帶不帶lambda有什么區(qū)別,其實(shí)兩者幾乎一致,我們以查詢?yōu)槔?,也就是?duì)比一下LambdaQueryWrapper
和QueryWrapper
,不難發(fā)現(xiàn),兩者的差異重點(diǎn)是因?yàn)閮扇藢?shí)現(xiàn)Query接口的定義不一樣
普通的構(gòu)造器實(shí)現(xiàn)query,定義了只能使用String,而Lambda構(gòu)造器的入?yún)t是允許是一個(gè)function函數(shù)。這樣的區(qū)別,將導(dǎo)致兩種不同的寫(xiě)法,見(jiàn)如下:
// 普通構(gòu)造器 QueryWrapper<CsdnUserInfo> wrapper = new QueryWrapper<>(); wrapper.eq("is_delete", 0); wrapper.orderByDesc("user_weight"); return this.list(wrapper); // lambda構(gòu)造器 LambdaQueryWrapper<CsdnUserInfo> lambdaWrapper = new LambdaQueryWrapper<>(); lambdaWrapper.eq(CsdnUserInfo::getIsDelete, 0); lambdaWrapper.orderByDesc(CsdnUserInfo::getUserWeight); return this.list(lambdaWrapper);
普通構(gòu)造器在使用時(shí),字段名只能直接寫(xiě)字符串,而lambda構(gòu)造器只能寫(xiě)方法。因?yàn)橹苯訉?xiě)字符串出錯(cuò)了不容易發(fā)現(xiàn),因此推薦大家還是盡量使用lambda構(gòu)造器,以方便在編碼階段就減少錯(cuò)漏可能性
3. query構(gòu)造器與update構(gòu)造器
首先兩者因?yàn)槎祭^承了 AbstractWrapper
的,所以大部分的SQL功能兩者是都具備的,比如SQL中where子句、join、排序那些東西。兩者最大的區(qū)別是它們分別實(shí)現(xiàn)了兩個(gè)不同的接口,一個(gè)繼承Query
,一個(gè)繼承Update
不難看出,兩者核心的不同就是語(yǔ)法的區(qū)別,查詢哪些字段或更新哪些字段
二、使用方式
關(guān)于構(gòu)造器的基本方法,內(nèi)容非常多,我們不再做搬運(yùn)工,大家可以直接去下面的官網(wǎng)看方法的文檔:條件構(gòu)造器文檔
1. 基礎(chǔ)使用
我們以舉例說(shuō)明,比方說(shuō)現(xiàn)在我們想要實(shí)現(xiàn)一個(gè)查詢功能,根據(jù)以下條件來(lái)獲取學(xué)生的信息:
性別為男性; 年齡在18到20歲之間; 成績(jī)大于80分; 班級(jí)為A班,然后按成績(jī)由高到底排
使用QueryWrapper
來(lái)實(shí)現(xiàn)這個(gè)查詢的代碼如下
QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("gender", "男性") .between("age", 18, 20) .gt("score", 80) .eq("class", "A班") .orderByDesc("score"); List<Student> students = studentMapper.selectList(queryWrapper);
又比如說(shuō) 我們想將姓名為"張三"的學(xué)生年齡更新為20。則可以使用UpdateWrapper
實(shí)現(xiàn)
String name = "張三"; Integer newAge = 20; UpdateWrapper<Student> updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("name", name) .set("age", newAge); int result = studentMapper.update(null, updateWrapper);
2. 易錯(cuò)點(diǎn)-邏輯范圍
由于條件構(gòu)造器的語(yǔ)法非常符合自然語(yǔ)言,所以有的時(shí)候反而讓人疏忽,我們?nèi)耘e一個(gè)例子,比如我們想獲取這樣的用戶
用戶狀態(tài)為有效的,名字為 張三 或 李四 或 王五 的用戶
你可能想當(dāng)然寫(xiě)成如下的樣子
// 錯(cuò)誤寫(xiě)法 Set<String> set = new HashSet<>(); set.add("zhangsan"); set.add("lisi"); set.add("wangwu"); LambdaQueryWrapper<CsdnUserInfo> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(CsdnUserInfo::getIsDelete, 0); for (String name : set) { wrapper.or(item -> item.eq(CsdnUserInfo::getUserName, name)); } return this.list(wrapper);
但事實(shí)上,最終預(yù)編譯產(chǎn)生的SQL是這樣的:
SELECT id,user_name,nick_name,like_status,collect_status,comment_status,user_weight,user_home_url,curr_blog_url,article_type,create_time,update_time,is_delete FROM csdn_user_info WHERE (is_delete = ? OR (user_name = ?) OR (user_name = ?) OR (user_name = ?))
不難發(fā)現(xiàn)這樣我們用戶狀態(tài)篩選 和 用戶名篩選 用 or 關(guān)聯(lián)起來(lái)了,這樣其實(shí)就會(huì)查出所有有效的用戶,不符合我們的預(yù)期。產(chǎn)生這種問(wèn)題的原因,其實(shí)是少用了個(gè)括號(hào)。我們應(yīng)該把用戶名的 or 限制在一個(gè)括號(hào)內(nèi),不能讓它擴(kuò)散出去。
此時(shí)我們可以使用 nested
來(lái)修復(fù)這個(gè)問(wèn)題,把我們的for循環(huán)扔進(jìn) nested
中
Set<String> set = new HashSet<>(); set.add("zhangsan"); set.add("lisi"); set.add("wangwu"); wrapper.eq(CsdnUserInfo::getIsDelete, 0); wrapper.nested(wp -> { for (String name : set) { wp.or(item -> item.eq(CsdnUserInfo::getUserName, name)); } }); return this.list(wrapper);
這樣最后的編譯的SQL是這樣的
SELECT id,user_name,nick_name,like_status,collect_status,comment_status,user_weight,user_home_url,curr_blog_url,article_type,create_time,update_time,is_delete FROM csdn_user_info WHERE (is_delete = ? AND ((user_name = ?) OR (user_name = ?) OR (user_name = ?)))
3. 易錯(cuò)點(diǎn)-null處理
使用 MybatisPlus 還有一個(gè)坑點(diǎn)容易被忽略,那就是 null 值的處理,比如我們?cè)诓迦牖蚋乱粭l數(shù)據(jù)時(shí),如果我們對(duì)象內(nèi)某一個(gè)字段為null,這個(gè)null值可能不會(huì)插入或更新進(jìn)表中。如果 null 在你的表中有業(yè)務(wù)意義,此刻就格外需要注意了。
比如我們想把張三的昵稱置為空,寫(xiě)了這么一段代碼
CsdnUserInfo user = new CsdnUserInfo(); user.setId(99999); user.setUserName("張三"); user.setNickName(null); csdnUserInfoService.updateById(user);
但我們的目的是不會(huì)生效的,因?yàn)榇藭r(shí) NickName 根本不會(huì)更新,它的SQL是這樣的
UPDATE csdn_user_info SET user_name=? WHERE id=?
此時(shí)我們可以在實(shí)體類上加一個(gè)注解 @TableField(updateStrategy= FieldStrategy.IGNORED)
這是因?yàn)閷?duì)字段的變更默認(rèn)會(huì)有空值校驗(yàn),只有顯示的指定為 忽略校驗(yàn) 才能把空值更新進(jìn)表中
經(jīng)過(guò)這樣的改動(dòng),我們?cè)賮?lái)看看編譯的SQL
UPDATE csdn_user_info SET user_name=?, nick_name=? WHERE id=?
nick_name 的 null 就可以成功更新進(jìn)表中了。
三、生效原理
如果你看過(guò)我們之前的《springboot基于Mybatis mysql實(shí)現(xiàn)讀寫(xiě)分離》,就不難理解,我們其實(shí)可以把整個(gè) MybatisPlus 分為項(xiàng)目啟動(dòng)階段做的準(zhǔn)備階段,和真正要執(zhí)行某段SQL的執(zhí)行階段。準(zhǔn)備階段
就是把我們寫(xiě)在xml的SQL進(jìn)行解析,構(gòu)造出一個(gè)個(gè)映射聲明(Mappedstatement),里面包含了我們的SQL主體。執(zhí)行階段
則是通過(guò)方法全稱找到自己的映射聲明(Mappedstatement),對(duì)其進(jìn)行拼接形成真正的可執(zhí)行SQL。
那在MybatisPlus下,我們明明沒(méi)寫(xiě)SQL,SQL又是從哪來(lái)的呢?
正如上面說(shuō)的,比如一個(gè)簡(jiǎn)單的插入,我們沒(méi)有寫(xiě)SQL,甚至連xml文件都沒(méi)有,而是直接使用的BaseMapper 中的 insert 方法。
所以在啟動(dòng)的時(shí)候,如下面的 service其實(shí)就在做準(zhǔn)備工作了,如果說(shuō)我們以前的xml文件寫(xiě)了現(xiàn)成的SQL語(yǔ)句,從而解析XML文件內(nèi)容,產(chǎn)生mappedStatement,
而像下面這種service其實(shí)也是這樣,不過(guò)他多了一步,它得先把service翻譯成xml格式,然后再來(lái)轉(zhuǎn)成mappedStatement。
@Service public class AlgorithmicProblemServiceImpl extends ServiceImpl<AlgorithmicProblemMapper, AlgorithmicProblem> implements AlgorithmicProblemService { }
就拿上面的 insert 舉例,MybatisPlus 中有一個(gè) insert 類,即 com.baomidou.mybatisplus.core.injector.methods.Insert 其中就有關(guān)鍵方法
1. 獲取語(yǔ)法模板SQL
所謂獲取模板SQL,其實(shí)就是在MybatisPlus中內(nèi)置了很多方法的基本語(yǔ)法,這些內(nèi)容只要在填上對(duì)應(yīng)的表名、字段名就能構(gòu)成一個(gè)基礎(chǔ) SQL 的輪廓了
還是以我們的插入語(yǔ)句為例,比如我們想插入一條數(shù)據(jù),它的基礎(chǔ)語(yǔ)法模板就是
"<script>\nINSERT INTO %s %s VALUES %s\n</script>"
其中三個(gè) ‘%s’ 就是待定的表名和字段名信息,將在后面代入表信息
2. 代入表及字段信息
我們上面看到了這樣一句話,它的作用就是根據(jù)語(yǔ)法模板,和表信息,構(gòu)造出一個(gè)基礎(chǔ)的SQL
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
上面的第一個(gè)入?yún)?sqlMethod.getSql() 就是我們上面說(shuō)的語(yǔ)法模板,第二個(gè)入?yún)⑹潜砻退淖侄蝿t是根據(jù)字段名拼出來(lái)的SQL,表名的話,我們?cè)趯?shí)體類里已經(jīng)寫(xiě)了,即 @TableName 注解后面的內(nèi)容
@Data @ApiModel("算法題實(shí)體類") @TableName("algorithmic_problem") public class AlgorithmicProblem extends Model<AlgorithmicProblem> { @ApiModelProperty("主鍵id") private Integer id; @ApiModelProperty("問(wèn)題名稱") private String questionName; @ApiModelProperty("問(wèn)題類型") private String questionType; @ApiModelProperty("1~10的分值") private Integer degreeOfImportance; @ApiModelProperty("1:簡(jiǎn)單;2:中等;3:困難") private Integer degreeOfDifficulty; @ApiModelProperty("困難指數(shù)") private Integer difficultyOfScore; @ApiModelProperty("力扣的問(wèn)題號(hào)") private Integer leetcodeNumber; @ApiModelProperty("力扣的問(wèn)題鏈接") private String leetcodeLink; @ApiModelProperty("標(biāo)簽") private String tag; @ApiModelProperty("創(chuàng)建時(shí)間") private Date createTime; @ApiModelProperty("邏輯刪除,0未刪除,1已刪除") private Integer isDelete; }
而后面的 columnScript
, valuesScript
則是根據(jù)我們的字段值,生成的一些腳本,其中包含了我們以前在mybatis 的 xml 文件中會(huì)寫(xiě)的動(dòng)態(tài)標(biāo)簽,這些標(biāo)簽的作用可以參考此片文章《數(shù)據(jù)庫(kù)操作不再困難,MyBatis動(dòng)態(tài)Sql標(biāo)簽解析》
通過(guò)上面的操作,不難發(fā)現(xiàn),我們以前在XML文件里需要寫(xiě)的 SQL 在此刻就被拼接出來(lái)了,它們形式是一樣的。需要注意的是,這里的字段全都是非空才會(huì)插入的。
<script> INSERT INTO algorithmic_problem <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if><if test="questionName != null">question_name,</if> <if test="questionType != null">question_type,</if> <if test="degreeOfImportance != null">degree_of_importance,</if> <if test="degreeOfDifficulty != null">degree_of_difficulty,</if> <if test="difficultyOfScore != null">difficulty_of_score,</if> <if test="leetcodeNumber != null">leetcode_number,</if> <if test="leetcodeLink != null">leetcode_link,</if> <if test="tag != null">tag,</if> <if test="createTime != null">create_time,</if> <if test="isDelete != null">is_delete,</if> </trim> VALUES <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> #{id}, </if><if test="questionName != null">#{questionName},</if> <if test="questionType != null">#{questionType},</if> <if test="degreeOfImportance != null">#{degreeOfImportance},</if> <if test="degreeOfDifficulty != null">#{degreeOfDifficulty},</if> <if test="difficultyOfScore != null">#{difficultyOfScore},</if> <if test="leetcodeNumber != null">#{leetcodeNumber},</if> <if test="leetcodeLink != null">#{leetcodeLink},</if> <if test="tag != null">#{tag},</if> <if test="createTime != null">#{createTime},</if> <if test="isDelete != null">#{isDelete},</if> </trim> </script>
3. 代入條件構(gòu)造器邏輯
上面的部分對(duì)于簡(jiǎn)單的 insert來(lái)說(shuō),其實(shí)已經(jīng)夠用了,但是對(duì)于一些用戶的查詢或修改邏輯,比如我們?cè)?servece 中寫(xiě)的那些篩選條件,排序等,它們又是如何起作用的呢?這里我們要分兩個(gè)階段來(lái)說(shuō):啟動(dòng)階段
、執(zhí)行階段
1. 啟動(dòng)階段的 “ew” 參數(shù)
首先,我們?cè)诖a中寫(xiě)的所有wrapper
(條件構(gòu)造器),這些wrapper包含了篩選條件,排序規(guī)則之類的,其實(shí)最終都被視為一個(gè)參數(shù)“ew”(Entity Wrapper)放入BaseMapper中進(jìn)行操作
// service 層 @Override public List<CsdnUserInfo> allUser() { QueryWrapper<CsdnUserInfo> wrapper = new QueryWrapper<>(); wrapper.eq("is_delete", 0); wrapper.orderByDesc("user_weight"); return this.list(wrapper); } // IService 類 default List<T> list(Wrapper<T> queryWrapper) { return this.getBaseMapper().selectList(queryWrapper); } // BaseMapper 類 List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
這個(gè)ew
參數(shù)在下面就是核心,我們上面提到了,在應(yīng)用啟動(dòng)階段,經(jīng)過(guò)基本語(yǔ)法和表的代入后,可以形成一些基礎(chǔ)SQL腳本。而對(duì)于像selectList 這種,會(huì)帶有ew
參數(shù)的,他們的基礎(chǔ)SQL就會(huì)比較復(fù)雜了,它們會(huì)在SQL中大量預(yù)留ew
參數(shù)相關(guān)的內(nèi)容。比如selectList 最終就會(huì)生成如下的一大串腳本:
<script> <if test="ew != null and ew.sqlFirst != null"> ${ew.sqlFirst} </if> SELECT <choose> <when test="ew != null and ew.sqlSelect != null"> ${ew.sqlSelect} </when> <otherwise>id,user_name,nick_name,like_status,collect_status,comment_status,user_weight,user_home_url,curr_blog_url,article_type,create_time,update_time,is_delete</otherwise> </choose> FROM csdn_user_info <if test="ew != null"> <where> <if test="ew.entity != null"> <if test="ew.entity.id != null">id=#{ew.entity.id}</if> <if test="ew.entity['userName'] != null"> AND user_name=#{ew.entity.userName}</if> <if test="ew.entity['nickName'] != null"> AND nick_name=#{ew.entity.nickName}</if> <if test="ew.entity['likeStatus'] != null"> AND like_status=#{ew.entity.likeStatus}</if> <if test="ew.entity['collectStatus'] != null"> AND collect_status=#{ew.entity.collectStatus}</if> <if test="ew.entity['commentStatus'] != null"> AND comment_status=#{ew.entity.commentStatus}</if> <if test="ew.entity['userWeight'] != null"> AND user_weight=#{ew.entity.userWeight}</if> <if test="ew.entity['userHomeUrl'] != null"> AND user_home_url=#{ew.entity.userHomeUrl}</if> <if test="ew.entity['currBlogUrl'] != null"> AND curr_blog_url=#{ew.entity.currBlogUrl}</if> <if test="ew.entity['articleType'] != null"> AND article_type=#{ew.entity.articleType}</if> <if test="ew.entity['createTime'] != null"> AND create_time=#{ew.entity.createTime}</if> <if test="ew.entity['updateTime'] != null"> AND update_time=#{ew.entity.updateTime}</if> <if test="ew.entity['isDelete'] != null"> AND is_delete=#{ew.entity.isDelete}</if> </if> <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere"> <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND </if> ${ew.sqlSegment} </if> </where> <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere"> ${ew.sqlSegment} </if> </if> <if test="ew != null and ew.sqlComment != null"> ${ew.sqlComment} </if> </script>
這里面,我們看到這段SQL中,涉及ew
參數(shù)的有這么幾個(gè)非常重要的元素:
ew.sqlFirst
:表示SQL片段,可以自定義SQL片段放在SQL的最開(kāi)始位置ew.sqlSelect
:表示要查詢的字段,默認(rèn)為"*",即查詢所有字段。可以通過(guò)該屬性指定要查詢的字段,多個(gè)字段之間用逗號(hào)分隔ew.entity
:表示要查詢的實(shí)體對(duì)象??梢酝ㄟ^(guò)該屬性指定要查詢的實(shí)體對(duì)象,可以根據(jù)實(shí)體對(duì)象的屬性進(jìn)行條件封裝ew.sqlSegment
:表示SQL片段,可以自定義SQL片段,用于在動(dòng)態(tài)SQL中添加自定義的SQL語(yǔ)句ew.sqlComment
:表示SQL注釋,可以為SQL語(yǔ)句添加注釋??梢酝ㄟ^(guò)該屬性指定要為SQL語(yǔ)句添加的注釋內(nèi)容。
2. 執(zhí)行階段的 “ew” 參數(shù)
以我們上面提到過(guò)的代碼為例
public List<CsdnUserInfo> allUser() { QueryWrapper<CsdnUserInfo> wrapper = new QueryWrapper<>(); wrapper.eq("is_delete", 0); wrapper.orderByDesc("user_weight"); return this.list(wrapper); }
這樣的 wrapper 最終會(huì)形成這樣一個(gè)對(duì)象,我們?yōu)樗砑恿藘蓚€(gè)屬性,即
有些眼尖的同學(xué)發(fā)現(xiàn)了,xml中用的是 ew.sqlSegment
, 而我們這里的條件全部都進(jìn)了 ew.expression
中,這兩也沒(méi)對(duì)上啊。其實(shí)在條件構(gòu)造器中 ew.sqlSegment = expression.getSqlSegment + lastSql
// AbstractWrapper public String getSqlSegment() { return this.expression.getSqlSegment() + this.lastSql.getStringValue(); }
而 expression.getSqlSegment() 又是其下的各個(gè)小 Segment 拼出來(lái)的
this.sqlSegment = this.normal.getSqlSegment() + this.groupBy.getSqlSegment() + this.having.getSqlSegment() + this.orderBy.getSqlSegment();
如此一來(lái),利用ognl,篩選條件也被我們拼湊出來(lái)了,最后拼接出 ew.sqlSegment 實(shí)際解析成為了一個(gè)字符串
(is_delete = #{ew.paramNameValuePairs.MPGENVAL1}) ORDER BY user_weight DESC
這個(gè)時(shí)候,又有眼尖的同學(xué)發(fā)現(xiàn)了,我們?cè)诖a中明明已經(jīng)寫(xiě)死了 is_delete = 0,為什么解析完卻成了 is_delete = #{ew.paramNameValuePairs.MPGENVAL1}
實(shí)際上這里有一個(gè)兼容的作用,我們可以在業(yè)務(wù)代碼里寫(xiě) wrapper.eq(“is_delete”, 0),也可以填上一個(gè)未知的變量 wrapper.eq(“is_delete”, var1),我們都知道為了防止SQL注入,實(shí)際上對(duì)于入?yún)⑽覀兌际窃谧詈蟛盘峤贿M(jìn)SQL中的,不可能因?yàn)槟闶菍?xiě)死的0,就提前把0填寫(xiě)在這里。所以,此處會(huì)先把你填的東西存放在 paramNameValuePairs
參數(shù)鍵值對(duì)中,再后面提交SQL的時(shí)候才取出來(lái)。其源碼如下:
// AbstractWrapper // 把一個(gè)條件翻譯sqlSegment,以上述wrapper.eq("is_delete", 0)為例,此處column 為字符串“is_delete” ;sqlKeyword 為EQ的枚舉值,會(huì)被翻譯成字符串,最后的參數(shù)值則會(huì)被翻譯成成如 {ew.paramNameValuePairs.MPGENVAL}= protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) { return this.maybeDo(condition, () -> { this.appendSqlSegments(this.columnToSqlSegment(column), sqlKeyword, () -> { return this.formatParam((String)null, val); }); }); } protected final String formatParam(String mapping, Object param) { String genParamName = "MPGENVAL" + this.paramNameSeq.incrementAndGet(); String paramStr = this.getParamAlias() + ".paramNameValuePairs." + genParamName; this.paramNameValuePairs.put(genParamName, param); return SqlScriptUtils.safeParam(paramStr, mapping); }
那么現(xiàn)在我們已經(jīng)擁有了兩件東西:1.一個(gè)大而全的基礎(chǔ)SQL語(yǔ)句(里面依賴了大量的ew內(nèi)容),2.一個(gè)完整的“ew”對(duì)象。同時(shí)擁有這兩者后,就能夠利用 Mybatis 的動(dòng)態(tài)解析能力去構(gòu)建出一個(gè)真正的可執(zhí)行的SQL了
總結(jié)
本次我們介紹了MybatisPlus 構(gòu)造器wrapper的使用方式及其易錯(cuò)點(diǎn),同時(shí)也針對(duì)其運(yùn)行的原理進(jìn)行了解釋,只有深刻理解了它的原理,我們才能更靈活的使用,并且更快的排查出問(wèn)題。所以也希望大家能結(jié)合源碼再思考一下,以便更好地掌握這部分內(nèi)容。
到此這篇關(guān)于MybatisPlus 構(gòu)造器wrapper的使用與原理的文章就介紹到這了,更多相關(guān)MybatisPlus 構(gòu)造器wrapper內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MybatisPlus中QueryWrapper常用方法總結(jié)
- MyBatisPlus-QueryWrapper多條件查詢及修改方式
- MybatisPlus自帶的queryWrapper實(shí)現(xiàn)時(shí)間倒序方式
- mybatisplus where QueryWrapper加括號(hào)嵌套查詢方式
- mybatisplus如何在xml的連表查詢中使用queryWrapper
- 關(guān)于QueryWrapper,實(shí)現(xiàn)MybatisPlus多表關(guān)聯(lián)查詢方式
- MybatisPlus?LambdaQueryWrapper使用int默認(rèn)值的坑及解決
相關(guān)文章
Springboot利用Aop捕捉注解實(shí)現(xiàn)業(yè)務(wù)異步執(zhí)行
在開(kāi)發(fā)過(guò)程中,盡量會(huì)將比較耗時(shí)且并不會(huì)影響請(qǐng)求的響應(yīng)結(jié)果的業(yè)務(wù)放在異步線程池中進(jìn)行處理,那么到時(shí)什么任務(wù)在執(zhí)行的時(shí)候會(huì)創(chuàng)建單獨(dú)的線程進(jìn)行處理呢?這篇文章主要介紹了Springboot利用Aop捕捉注解實(shí)現(xiàn)業(yè)務(wù)異步執(zhí)行2023-04-04Java生產(chǎn)者消費(fèi)者的三種實(shí)現(xiàn)方式
這篇文章主要介紹了Java生產(chǎn)者消費(fèi)者的三種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07詳談Java中Object類中的方法以及finalize函數(shù)作用
下面小編就為大家?guī)?lái)一篇詳談Java中Object類中的方法以及finalize函數(shù)作用。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04Java實(shí)現(xiàn)在線語(yǔ)音識(shí)別
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)在線語(yǔ)音識(shí)別功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Springboot詳細(xì)講解RocketMQ實(shí)現(xiàn)順序消息的發(fā)送與消費(fèi)流程
RocketMQ作為一款純java、分布式、隊(duì)列模型的開(kāi)源消息中間件,支持事務(wù)消息、順序消息、批量消息、定時(shí)消息、消息回溯等,本篇我們了解如何實(shí)現(xiàn)順序消息的發(fā)送與消費(fèi)2022-06-06