一、回顧
現(xiàn)在越來(lái)越流行基于 SpringBoot 開(kāi)發(fā) Web 應(yīng)用畜普,其中利用 Mybatis 作為數(shù)據(jù)庫(kù) CRUD 操作已成為主流唉韭。樓主以 MySQL 為例柔逼,總結(jié)了九大類使用 Mybatis 操作數(shù)據(jù)庫(kù) SQL 小技巧分享給大家。
- 分頁(yè)查詢
- 預(yù)置 sql 查詢字段
- 一對(duì)多級(jí)聯(lián)查詢
- 一對(duì)一級(jí)聯(lián)查詢
- foreach 搭配 in 查詢
- 利用if 標(biāo)簽拼裝動(dòng)態(tài) where 條件
- 利用 choose 和 otherwise組合標(biāo)簽拼裝查詢條件
- 動(dòng)態(tài)綁定查詢參數(shù):_parameter
- 利用 set 配合 if 標(biāo)簽享钞,動(dòng)態(tài)設(shè)置數(shù)據(jù)庫(kù)字段更新值
01 分頁(yè)查詢
利用 limit 設(shè)置每頁(yè) offset 偏移量和每頁(yè) size 大小揍诽。
select * from sys_user u
LEFT JOIN sys_user_site s ON u.user_id = s.user_id
LEFT JOIN sys_dept d ON d.dept_id = s.dept_id
LEFT JOIN sys_emailinfo e ON u.user_id = e.userid AND e.MAIN_FLAG = 'Y'
<where>
<include refid="userCondition"/>
</where>
limit #{offset}, #{limit}
02 預(yù)置 sql 查詢字段
<sql id="columns">
id,title,content,original_img,is_user_edit,province_id,status,porder
</sql>
查詢 select 語(yǔ)句引用 columns:
<select id="selectById" resultMap="RM_MsShortcutPanel">
seelct
<include refid="columns"/>
from cms_self_panel
where
id = #{_parameter}
</select>
03 一對(duì)多級(jí)聯(lián)查詢
利用 mybatis 的 collection 標(biāo)簽,可以在每次查詢文章主體同時(shí)通過(guò) queryparaminstancelist 級(jí)聯(lián)查詢出關(guān)聯(lián)表數(shù)據(jù)栗竖。
<resultMap id="BaseResultMap" type="com.unicom.portal.pcm.entity.ArticleEntity">
<id column="id" jdbcType="BIGINT" property="id"/>
<collection property="paramList" column="id" select="queryparaminstancelist"/>
</resultMap>
queryparaminstancelist 的 sql 語(yǔ)句
<select id="queryparaminstancelist" resultMap="ParamInstanceResultMap">
select * from `cms_article_flow_param_instance` where article_id=#{id}
</select>
04 一對(duì)一級(jí)聯(lián)查詢
利用 mybatis 的 association 標(biāo)簽暑脆,一對(duì)一查詢關(guān)聯(lián)表數(shù)據(jù)。
<resultMap id="BaseResultMap" type="com.unicom.portal.pcm.entity.ArticleEntity">
<association property="articleCount" javaType="com.unicom.portal.pcm.entity.MsArticleCount"/>
</resultMap>
查詢sql語(yǔ)句:
MsArticlecount 實(shí)體對(duì)象的屬性值可以從 上面的 select 后的 sql 字段進(jìn)行匹配映射獲取狐肢。
05 foreach 搭配 in 查詢
利用 foreach 遍歷 array 集合的參數(shù)添吗,拼成 in 查詢條件
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
06 利用 if 標(biāo)簽拼裝動(dòng)態(tài) where 條件
select r.*, (select d.org_name from sys_dept d where d.dept_id = r.dept_id) deptName from sys_role r
<where>
r.wid = #{wid}
<if test="roleName != null and roleName.trim() != ''">
and r.`role_name` like concat('%',#{roleName},'%')
</if>
<if test="status != null and status.trim() != ''">
and r.`status` = #{status}
</if>
</where>
07 利用 choose 和 otherwise 組合標(biāo)簽拼裝查詢條件
<choose>
<when test="sidx != null and sidx.trim() != ''">
order by r.${sidx} ${order}
</when>
<otherwise>
order by r.role_id asc
</otherwise>
</choose>
08 隱形綁定參數(shù):_parameter
_parameter 參數(shù)的含義
“
當(dāng) Mapper、association处坪、collection 指定只有一個(gè)參數(shù)時(shí)進(jìn)行查詢時(shí)根资,可以使用 _parameter,它就代表了這個(gè)參數(shù)同窘。
另外玄帕,當(dāng)使用 Mapper指定方法使用 @Param 的話,會(huì)使用指定的參數(shù)值代替想邦。
SELECT id, grp_no grpNo, province_id provinceId, status FROM tj_group_province
<where>
...
<if test="_parameter!=null">
and grp_no = #{_parameter}
</if>
</where>
09 利用 set 配合 if 標(biāo)簽裤纹,動(dòng)態(tài)設(shè)置數(shù)據(jù)庫(kù)字段更新值
<update id="updateById">
UPDATE cms_label
<set>
<if test="labelGroupId != null">
label_group_id = #{labelGroupId},
</if>
dept_id = #{deptId},
<if test="recommend != null">
is_recommend = #{recommend},
</if>
</set>
WHERE label_id = #{labelId}
</update
二、Mybatis-Plus Lambda 表達(dá)式理論篇
背景
如果 Mybatis-Plus 是扳手,那 Mybatis Generator 就是生產(chǎn)扳手的工廠鹰椒。
MyBatis 是一種操作數(shù)據(jù)庫(kù)的 ORM 框架锡移,提供一種 Mapper 類,支持讓你用 java 代碼進(jìn)行增刪改查的數(shù)據(jù)庫(kù)操作漆际,省去了每次都要手寫 sql 語(yǔ)句的麻煩淆珊。但是有一個(gè)前提,你得先在 xml 中寫好 sql 語(yǔ)句奸汇,也是很麻煩的施符。
題外話:Mybatis 和 Hibernate 的比較
- Mybatis 是一個(gè)半 ORM 框架;Hibernate 是一個(gè)全 ORM 框架擂找。Mybatis 需要自己編寫 sql 戳吝。
- Mybatis 直接編寫原生 sql,靈活度高贯涎,可以嚴(yán)格控制 sql 執(zhí)行性能听哭;Hibernate的自動(dòng)生成 hql,因?yàn)楦玫姆庋b型塘雳,開(kāi)發(fā)效率提高的同時(shí)陆盘,sql 語(yǔ)句的調(diào)優(yōu)比較麻煩。
- Hibernate的 hql 數(shù)據(jù)庫(kù)移植性比 Mybatis 更好粉捻,Hibernate 的底層對(duì) hql 進(jìn)行了處理礁遣,對(duì)于數(shù)據(jù)庫(kù)的兼容性更好,
- Mybatis 直接寫的原生 sql 都是與數(shù)據(jù)庫(kù)相關(guān)肩刃,不同數(shù)據(jù)庫(kù) sql 不同祟霍,這時(shí)就需要多套 sql 映射文件。
- Hibernate 在級(jí)聯(lián)刪除的時(shí)候效率低盈包;數(shù)據(jù)量大沸呐, 表多的時(shí)候,基于關(guān)系操作會(huì)變得復(fù)雜呢燥。
- Mybatis 和 Hibernate 都可以使用第三方緩存崭添,而 Hibernate 相比 Mybatis 有更好的二級(jí)緩存機(jī)制。
微信搜公眾號(hào)「猿芯」叛氨,后臺(tái)私信回復(fù) 1024 免費(fèi)領(lǐng)取 SpringCloud呼渣、SpringBoot,微信小程序寞埠、Java面試屁置、數(shù)據(jù)結(jié)構(gòu)、算法等全套視頻資料仁连。
為什么要選擇 Lambda 表達(dá)式蓝角?
Mybatis-Plus 的存在就是為了稍稍彌補(bǔ) Mybatis 的不足。
在我們使用 Mybatis 時(shí)會(huì)發(fā)現(xiàn),每當(dāng)要寫一個(gè)業(yè)務(wù)邏輯的時(shí)候都要在 DAO 層寫一個(gè)方法使鹅,再對(duì)應(yīng)一個(gè) SQL揪阶,即使是簡(jiǎn)單的條件查詢、即使僅僅改變了一個(gè)條件都要在 DAO層新增一個(gè)方法患朱,針對(duì)這個(gè)問(wèn)題鲁僚,Mybatis-Plus 就提供了一個(gè)很好的解決方案:lambda 表達(dá)式,它可以讓我們避免許多重復(fù)性的工作麦乞。
想想 Mybatis 官網(wǎng)提供的 CRUD 例子吧蕴茴,基本上 xml 配置占據(jù)了絕大部分劝评。而用 Lambda 表達(dá)式寫的 CRUD 代碼非常簡(jiǎn)潔姐直,真正做到零配置,不需要在 xml 或用注解(@Select)寫大量原生 SQL 代碼蒋畜。
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.like(UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like全包含關(guān)鍵字查詢::" + u.getUserName()));
lambda 表達(dá)式的理論基礎(chǔ)
Java中的 lambda 表達(dá)式實(shí)質(zhì)上是一個(gè)匿名方法声畏,但該方法并非獨(dú)立執(zhí)行,而是用于實(shí)現(xiàn)由函數(shù)式接口定義的唯一抽象方法姻成。
使用 lambda 表達(dá)式時(shí)插龄,會(huì)創(chuàng)建實(shí)現(xiàn)了函數(shù)式接口的一個(gè)匿名類實(shí)例,如 Java8 中的線程 Runnable 類實(shí)現(xiàn)了函數(shù)接口:@FunctionalInterface科展。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
平常我們執(zhí)行一個(gè) Thread 線程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("xxxx");
}
}).start();
如果用 lambda 會(huì)非常簡(jiǎn)潔均牢,一行代碼搞定。
new Thread(()-> System.out.println("xxx")).start();
所以在某些場(chǎng)景下使用 lambda 表達(dá)式真的能減少 java 中一些冗長(zhǎng)的代碼才睹,增加代碼的優(yōu)雅性徘跪。
lambda 條件構(gòu)造器基礎(chǔ)類:包裝器模式(裝飾模式)之 AbstractWrapper AbstractWrapper 條件構(gòu)造器說(shuō)明
- 出現(xiàn)的第一個(gè)入?yún)?boolean condition 表示該條件是否加入最后生成的 sql 中,例如:query.like(StringUtils.isNotBlank(name), Entity::getName, name) .eq(age!=null && age >= 0, Entity::getAge, age)
- 代碼塊內(nèi)的多個(gè)方法均為從上往下補(bǔ)全個(gè)別 boolean 類型的入?yún)?默認(rèn)為 true
- 出現(xiàn)的泛型 Param 均為 Wrapper 的子類實(shí)例(均具有 AbstractWrapper 的所有方法)
- 方法在入?yún)⒅谐霈F(xiàn)的 R 為泛型琅攘,在普通 wrapper 中是 String 垮庐,在 LambdaWrapper 中是函數(shù)(例:Entity::getId,Entity 為實(shí)體類坞琴,getId為字段id的getMethod)
- 方法入?yún)⒅械?R column 均表示數(shù)據(jù)庫(kù)字段哨查,當(dāng) R 具體類型為 String 時(shí)則為數(shù)據(jù)庫(kù)字段名(字段名是數(shù)據(jù)庫(kù)關(guān)鍵字的自己用轉(zhuǎn)義符包裹!)!而不是實(shí)體類數(shù)據(jù)字段名!!!,另當(dāng) R 具體類型為 SFunction 時(shí)項(xiàng)目 runtime 不支持 eclipse 自家的編譯器!
- 使用普通 wrapper剧辐,入?yún)?Map 和 List 的均以 json 形式表現(xiàn)!
- 使用中如果入?yún)⒌?Map 或者 List為空,則不會(huì)加入最后生成的 sql 中!
警告:
不支持以及不贊成在 RPC 調(diào)用中把 Wrapper 進(jìn)行傳輸寒亥。
“
Wrapper 很重 傳輸 Wrapper 可以類比為你的 controller 用 map 接收值(開(kāi)發(fā)一時(shí)爽,維護(hù)火葬場(chǎng)) 正確的 RPC 調(diào)用姿勢(shì)是寫一個(gè) DTO 進(jìn)行傳輸荧关,被調(diào)用方再根據(jù) DTO 執(zhí)行相應(yīng)的操作 我們拒絕接受任何關(guān)于 RPC 傳輸 Wrapper 報(bào)錯(cuò)相關(guān)的 issue 甚至 pr溉奕。
AbstractWrapper 內(nèi)部結(jié)構(gòu)
從上圖,我們了解到 AbstractWrapper 的實(shí)際上實(shí)現(xiàn)了五大接口:
- SQL 片段函數(shù)接口:ISqlSegment
@FunctionalInterface
public interface ISqlSegment extends Serializable {
/**
* SQL 片段
*/
String getSqlSegment();
}
- 比較值接口 Compare<Children, R>羞酗,如 等值 eq腐宋、不等于:ne、大于 gt、大于等于:ge胸竞、小于 lt欺嗤、小于等于 le、between卫枝、模糊查詢:like 等等
- 嵌套接口 Nested<Param, Children> 煎饼,如 and、or
- 拼接接口 Join<Children>校赤,如 or 吆玖、exists
- 函數(shù)接口 Func<Children, R>,如 in 查詢马篮、groupby 分組沾乘、having、order by排序等
微信搜公眾號(hào)「猿芯」浑测,后臺(tái)私信回復(fù) 1024 免費(fèi)領(lǐng)取 SpringCloud翅阵、SpringBoot,微信小程序迁央、Java面試掷匠、數(shù)據(jù)結(jié)構(gòu)、算法等全套視頻資料岖圈。
常用的 where 條件表達(dá)式 eq讹语、like、in蜂科、ne顽决、gt、ge崇摄、lt擎值、le。
@Override
public Children in(boolean condition, R column, Collection<?> coll) {
return doIt(condition, () -> columnToString(column), IN, inExpression(coll));
}
public Children notIn(boolean condition, R column, Collection<?> coll)
public Children inSql(boolean condition, R column, String inValue)
public Children notInSql(boolean condition, R column, String inValue)
public Children groupBy(boolean condition, R... columns)
public Children orderBy(boolean condition, boolean isAsc, R... columns)
public Children eq(boolean condition, R column, Object val)
public Children ne(boolean condition, R column, Object val)
public Children gt(boolean condition, R column, Object val)
public Children ge(boolean condition, R column, Object val)
public Children lt(boolean condition, R column, Object val)
public Children le(boolean condition, R column, Object val)
...
/**
* 普通查詢條件
*
* @param condition 是否執(zhí)行
* @param column 屬性
* @param sqlKeyword SQL 關(guān)鍵詞
* @param val 條件值
*/
protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) {
return doIt(condition, () -> columnToString(column), sqlKeyword, () -> formatSql("{0}", val));
}
SQL 片段函數(shù)接口
lambda 這么好用的秘訣在于 SQL 片段函數(shù)接口:ISqlSegment逐抑,我們?cè)?doIt 方法找到 ISqlSegment 對(duì)象參數(shù)鸠儿,翻開(kāi) ISqlSegment 源碼,發(fā)現(xiàn)它真實(shí)的廬山真面目厕氨,原來(lái)是基于 Java 8 的函數(shù)接口 @FunctionalInterface 實(shí)現(xiàn)进每!
ISqlSegment 就是對(duì) where 中的每個(gè)條件片段進(jìn)行組裝。
/**
* 對(duì)sql片段進(jìn)行組裝
*
* @param condition 是否執(zhí)行
* @param sqlSegments sql片段數(shù)組
* @return children
*/
protected Children doIt(boolean condition, ISqlSegment... sqlSegments) {
if (condition) {
expression.add(sqlSegments);
}
return typedThis;
}
@FunctionalInterface
public interface ISqlSegment extends Serializable {
/**
* SQL 片段
*/
String getSqlSegment();
}
從 MergeSegments 類中命斧,我們找到 getSqlSegment 方法田晚,其中代碼片段
sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment()
這段代碼表明,一條完整的 where 條件 SQL 語(yǔ)句国葬,最終由 normal SQL 片段,groupBy SQL 片段,having SQL 片段,orderBy SQL 片段拼接而成贤徒。
@Getter
@SuppressWarnings("serial")
public class MergeSegments implements ISqlSegment {
private final NormalSegmentList normal = new NormalSegmentList();
private final GroupBySegmentList groupBy = new GroupBySegmentList();
private final HavingSegmentList having = new HavingSegmentList();
private final OrderBySegmentList orderBy = new OrderBySegmentList();
@Getter(AccessLevel.NONE)
private String sqlSegment = StringPool.EMPTY;
@Getter(AccessLevel.NONE)
private boolean cacheSqlSegment = true;
public void add(ISqlSegment... iSqlSegments) {
List<ISqlSegment> list = Arrays.asList(iSqlSegments);
ISqlSegment firstSqlSegment = list.get(0);
if (MatchSegment.ORDER_BY.match(firstSqlSegment)) {
orderBy.addAll(list);
} else if (MatchSegment.GROUP_BY.match(firstSqlSegment)) {
groupBy.addAll(list);
} else if (MatchSegment.HAVING.match(firstSqlSegment)) {
having.addAll(list);
} else {
normal.addAll(list);
}
cacheSqlSegment = false;
}
@Override
public String getSqlSegment() {
if (cacheSqlSegment) {
return sqlSegment;
}
cacheSqlSegment = true;
if (normal.isEmpty()) {
if (!groupBy.isEmpty() || !orderBy.isEmpty()) {
sqlSegment = groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
}
} else {
sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
}
return sqlSegment;
}
}
三芹壕、Mybatis-Plus Lambda 表達(dá)式實(shí)戰(zhàn)
01 環(huán)境準(zhǔn)備
1. Maven 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
2. 實(shí)體(表)以及 Mapper 表映射文件
- Base 實(shí)體
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@SuperBuilder(toBuilder = true)
@Data
public class BaseEntity {
@TableField(value = "created_tm", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdTm;
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
@TableField(value = "modified_tm", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime modifiedTm;
@TableField(value = "modified_by", fill = FieldFill.INSERT_UPDATE)
private String modifiedBy;
}
- 用戶賬號(hào)實(shí)體:UserEntity
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@SuperBuilder(toBuilder = true)
@Data
@TableName("sys_user")
public class UserEntity extends BaseEntity{
private Long userId;
private String userName;
private Integer sex;
private Integer age;
private String mobile;
}
Mapper 操作類
List<UserDTO> selectUsers();
UserEntity selectByIdOnXml(long userId);
@Results(id = "userResult", value = {
@Result(property = "user_id", column = "userId", id = true),
@Result(property = "userName", column = "user_name"),
@Result(property = "sex", column = "sex"),
@Result(property = "mobile", column = "mobile"),
@Result(property = "age", column = "age")
})
@Select("select * from sys_user where user_id = #{id}")
UserEntity selectByIdOnSelectAnnotation(@Param("id") long id);
@SelectProvider(type = UserSqlProvider.class, method = "selectById")
@ResultMap("BaseResultMap")
UserEntity selectByIdOnSelectProviderAnnotation(long id);
@Select("select * from sys_user where user_id = #{id} and user_name=#{userName}")
@ResultMap("BaseResultMap")
UserEntity selectByIdOnParamAnnotation(@Param("id") long id, @Param("userName") String uerName);
Mapper 表映射文件
<mapper namespace="com.dunzung.mybatisplus.query.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.UserEntity">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="sex" property="sex"/>
<result column="age" property="age"/>
<result column="mobile" property="mobile"/>
</resultMap>
<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
<association property="card" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
<collection property="orders" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
</resultMap>
<select id="selectUsers" resultMap="RelationResultMap">
select * from sys_user
</select>
<select id="selectByIdOnXml" resultMap="BaseResultMap">
select * from sys_user where user_id = #{userId}
</select>
</mapper>
- 訂單實(shí)體:OrderEntity
@Data
@TableName("sys_user_card")
public class CardEntity {
private Long cardId;
private String cardCode;
private Long userId;
}
Mapper 操作類
@Mapper
public interface OrderMapper extends BaseMapper<OrderEntity> {
}
Mapper 表映射文件
<mapper namespace="com.dunzung.mybatisplus.query.mapper.OrderMapper">
<resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.OrderEntity">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<result column="user_id" property="userId"/>
<result column="price" property="price"/>
<result column="created_tm" property="createdTm"/>
</resultMap>
<select id="selectOrders" resultMap="BaseResultMap">
select * from biz_order where user_id = #{userId}
</select>
</mapper>
- 身份證實(shí)體:CardEntity
@Data
@TableName("biz_order")
public class OrderEntity {
private Long orderId;
private String orderName;
private Integer userId;
private Date createdTm;
private Integer price;
}
Mapper 操作類
@Mapper
public interface CardMapper extends BaseMapper<CardEntity> {
}
Mapper 表映射文件
<mapper namespace="com.dunzung.mybatisplus.query.mapper.CardMapper">
<resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.CardEntity">
<id column="card_id" property="cardId"/>
<result column="card_code" property="cardCode"/>
<result column="user_id" property="userId"/>
</resultMap>
<select id="selectCardByUserId" resultMap="BaseResultMap">
select * from sys_user_card where user_id = #{userId}
</select>
</mapper>
02 Lambda 基礎(chǔ)篇
lambda 構(gòu)建復(fù)雜的查詢條件構(gòu)造器:LambdaQueryWrapper
LambdaQueryWrapper 四種不同的 lambda 構(gòu)造方法
- 方式一 使用 QueryWrapper 的成員方法方法 lambda 構(gòu)建 LambdaQueryWrapper
LambdaQueryWrapper<UserEntity> lambda = new QueryWrapper<UserEntity>().lambda();
- 方式二 直接 new 出 LambdaQueryWrapper
LambdaQueryWrapper<UserEntity> lambda = new LambdaQueryWrapper<>();
- 方式三 使用 Wrappers 的靜態(tài)方法 lambdaQuery 構(gòu)建 LambdaQueryWrapper 推薦
LambdaQueryWrapper<UserEntity> lambda = Wrappers.lambdaQuery();
- 方式四:鏈?zhǔn)讲樵?/li>
List<UserEntity> users = new LambdaQueryChainWrapper<UserEntity>(userMapper)
.like(User::getName, "雨").ge(User::getAge, 20).list();
筆者推薦使用 Wrappers 的靜態(tài)方法 lambdaQuery 構(gòu)建 LambdaQueryWrapper 條件構(gòu)造器。
Debug 調(diào)試
為了 Debug 調(diào)試方便接奈,需要在 application.yml 啟動(dòng)文件開(kāi)啟 Mybatis-Plus SQL 執(zhí)行語(yǔ)句全棧打印:
#mybatis
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
執(zhí)行效果如下:
1 等值查詢:eq
@Test
public void testLambdaQueryOfEq() {
//eq查詢
//相當(dāng)于 select * from sys_user where user_id = 1
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getUserId, 1L);
UserEntity user = userMapper.selectOne(lqw);
System.out.println("eq查詢::" + user.getUserName());
}
eq 查詢等價(jià)于原生 sql 的等值查詢踢涌。
select * from sys_user where user_id = 1
2 范圍查詢 :in
@Test
public void testLambdaQueryOfIn() {
List<Long> ids = Arrays.asList(1L, 2L);
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.in(UserEntity::getUserId, ids);
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("in查詢::" + u.getUserName()));
}
in 查詢等價(jià)于原生 sql 的 in 查詢
select * from sys_user where user_id in (1,2)
3 通配符模糊查詢:like
@Test
public void testLambdaQueryOfLikeAll() {
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.like(UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like全包含關(guān)鍵字查詢::" + u.getUserName()));
}
like 查詢等價(jià)于原生 sql 的 like 全通配符模糊查詢。
select * from sys_user where sex = 0 and user_name like '%dun%'
4 右通配符模糊查詢:likeRight
@Test
public void testLambdaQueryOfLikeRight() {
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.likeRight(UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like Right含關(guān)鍵字查詢::" + u.getUserName()));
}
likeRight 查詢相當(dāng)于原生 sql 的 like 右通配符模糊查詢序宦。
select * from sys_user where sex = 0 and user_name like 'dun%'
5 左通配符模糊查詢:likeLeft
@Test
public void testLambdaQueryOfLikeLeft() {
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.likeLeft(UserEntity::getUserName, "zung");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like Left含關(guān)鍵字查詢::" + u.getUserName()));
}
likeLeft 查詢相當(dāng)于原生 sql 的 like 左通配符模糊查詢睁壁。
select * from sys_user where sex = 0 and user_name like '%zung'
6 條件判斷查詢
條件判斷查詢類似于 Mybatis 的 if 標(biāo)簽,第一個(gè)入?yún)?boolean condition 表示該條件是否加入最后生成的 sql 中互捌。
@Test
public void testLambdaQueryOfBoolCondition() {
UserEntity condition = UserEntity.builder()
.sex(1)
.build();
//eq 或 like 條件判斷查詢
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(condition.getSex() != null, UserEntity::getSex, 0L)
// 滿足 bool 判斷潘明,是否進(jìn)查詢按字段 userName 查詢
.like(condition.getUserName() != null, UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like查詢::" + u.getUserName()));
}
7 利用 or 和 and 構(gòu)建復(fù)雜的查詢條件
@Test
public void testLambdaQueryOfOr_And() {
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.and(wrapper->wrapper.eq(UserEntity::getUserName,"dunzung")
.or().ge(UserEntity::getAge, 50));
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like查詢::" + u.getUserName()));
}
上面實(shí)例查詢等價(jià)于原生 sql 查詢:
select * from sys_user where sex = 0 and (use_name = 'dunzung' or age >=50)
8 善于利用分頁(yè)利器 PageHelpler
@Test
public void testLambdaPage() {
//PageHelper分頁(yè)查詢
//相當(dāng)于 select * from sys_user limit 0,2
int pageNumber = 0;
int pageSize = 2;
PageHelper.startPage(pageNumber + 1, pageSize);
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(UserEntity::getAge)
.orderByDesc(UserEntity::getMobile);
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("page分頁(yè)查詢::" + u.getUserName()));
}
上面實(shí)例查詢等價(jià)于原生 sql 分頁(yè)查詢:
select * from sys_user order by age desc,mobile desc limit 0,2
另外,Mybatis-Plus 自帶分頁(yè)組件秕噪,BaseMapper 接口提供兩種分頁(yè)方法來(lái)實(shí)現(xiàn)物理分頁(yè)钳降。
- 第一個(gè)返回實(shí)體對(duì)象允許 null
- 第二個(gè)人返回 map 對(duì)象多用于在指定放回字段時(shí)使用,避免為指定字段 null 值出現(xiàn)
IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
注意巢价,Mybatis-Plus 自帶分頁(yè)組件時(shí)牲阁,需要配置 PaginationInterceptor 分頁(yè)插件。
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
9 更新條件構(gòu)造器:LambdaUpdateWrapper
@Test
public void testLambdaUpdate() {
LambdaUpdateWrapper<UserEntity> luw = Wrappers.lambdaUpdate();
luw.set(UserEntity::getUserName, "dunzung01")
.set(UserEntity::getSex, 1);
luw.eq(UserEntity::getUserId, 1);
userMapper.update(null, luw);
}
03 進(jìn)階篇
1. Association
Association 標(biāo)簽適用于表和表之間存在一對(duì)一的關(guān)聯(lián)關(guān)系壤躲,如用戶和身份證存在一個(gè)人只會(huì)有一個(gè)身份證號(hào),反過(guò)來(lái)也成立备燃。
@Test
public void testOnAssociationTag() {
List<UserDTO> userList = userMapper.selectUsers();
userList.forEach(u -> System.out.println(u.getUserName()));
}
XML配置
<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
<association property="card" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
</resultMap>
2. Collection
Collection 標(biāo)簽適用于表和表之間存在一對(duì)多的關(guān)聯(lián)關(guān)系碉克,如用戶和訂單存在一個(gè)人可以購(gòu)買多個(gè)物品,產(chǎn)生多個(gè)購(gòu)物訂單并齐。
@Test
public void testOnCollectionTag() {
List<UserDTO> userList = userMapper.selectUsers();
userList.forEach(u -> System.out.println(u.getUserName()));
}
XML配置
<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
<collection property="orders"
column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
</resultMap>
注意 Association 和 Collection 先后關(guān)系漏麦,在編寫 ResultMap 時(shí),association 在前况褪,collection 標(biāo)簽在后撕贞。
<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
<association property="card" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
<collection property="orders" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
</resultMap>
如果二者顛倒順序會(huì)提示錯(cuò)誤。
3. 元對(duì)象字段填充屬性值:MetaObjectHandler
MetaObjectHandler元對(duì)象字段填充器的填充原理是直接給 entity 的屬性設(shè)置值测垛,提供默認(rèn)方法的策略均為:
“
如果屬性有值則不覆蓋捏膨,如果填充值為 null 則不填充秉宿,字段必須聲明 TableField 注解跋破,屬性 fill 選擇對(duì)應(yīng)策略,該聲明告知 Mybatis-Plus 需要預(yù)留注入 SQL字段青扔。 TableField 注解則是指定該屬性在對(duì)應(yīng)情況下必有值锯七,如果無(wú)值則入庫(kù)會(huì)是 null链快。
自定義填充處理器 MyMetaObjectHandler 在 Spring Boot 中需要聲明 @Component 或 @Bean 注入,要想根據(jù)注解 FieldFill.xxx眉尸,如:
@TableField(value = "created_tm", fill = FieldFill.INSERT)
private LocalDateTime createdTm;
@TableField(value = "modified_tm", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime modifiedTm;
和字段名以及字段類型來(lái)區(qū)分必須使用父類的 setInsertFieldValByName 或者 setUpdateFieldValByName 方法域蜗,不需要根據(jù)任何來(lái)區(qū)分可以使用父類的 setFieldValByName 方法 巨双。
/**
* 屬性值填充 Handler
*
* @author 猿芯
* @since 2021/3/30
*/
@Component
public class FillMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setInsertFieldValByName("createdTm", LocalDateTime.now(), metaObject);
this.setInsertFieldValByName("createdBy", MvcContextHolder.getUserName(), metaObject);
this.setFieldValByName("modifiedTm", LocalDateTime.now(), metaObject);
this.setFieldValByName("modifiedBy", MvcContextHolder.getUserName(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setUpdateFieldValByName("modifiedTm", LocalDateTime.now(), metaObject);
this.setUpdateFieldValByName("modifiedBy", MvcContextHolder.getUserName(), metaObject);
}
}
一般 FieldFill.INSERT 用父類的 setInsertFieldValByName 方法更新創(chuàng)建屬性(創(chuàng)建人、創(chuàng)建時(shí)間)值霉祸;FieldFill.INSERT_UPDATE 用父類的 setUpdateFieldValByName 方法更新修改屬性(修改人炉峰、修改時(shí)間)值;如果想讓諸如 FieldFill.INSERT 或 FieldFill.INSERT_UPDATE 任何時(shí)候不起作用脉执,用父類的 setFieldValByName 設(shè)置屬性(創(chuàng)建人疼阔、創(chuàng)建時(shí)間、修改人半夷、修改時(shí)間)值即可婆廊。
微信搜公眾號(hào)「猿芯」,后臺(tái)私信回復(fù) 1024 免費(fèi)領(lǐng)取 SpringCloud巫橄、SpringBoot淘邻,微信小程序、Java面試湘换、數(shù)據(jù)結(jié)構(gòu)宾舅、算法等全套視頻資料。
4. 自定義SQL
使用 Wrapper 自定義 SQL 需要 mybatis-plus 版本 >= 3.0.7 彩倚,param 參數(shù)名要么叫 ew筹我,要么加上注解 @Param(Constants.WRAPPER) ,使用 ${ew.customSqlSegment} 不支持 Wrapper 內(nèi)的 entity生成 where 語(yǔ)句帆离。
注解方式
@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);
XML配置
List<MysqlData> getAll(Wrapper ew);
<select id="getAll" resultType="MysqlData">
SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>
四蔬蕊、Mybatis-Plus lambda 表達(dá)式的優(yōu)勢(shì)與劣勢(shì)
通過(guò)上面豐富的舉例詳解以及剖析 lambda 底層實(shí)現(xiàn)原理,想必大家會(huì)問(wèn):” lambda 表達(dá)式似乎只支持單表操作哥谷?”
據(jù)我對(duì) Mybatis-Plus 官網(wǎng)的了解岸夯,目前確實(shí)是這樣。依筆者實(shí)際運(yùn)用經(jīng)驗(yàn)來(lái)看们妥,其實(shí)程序員大部分開(kāi)發(fā)的功能基本上都是針對(duì)單表操作的猜扮,Lambda 表達(dá)式的優(yōu)勢(shì)在于幫助開(kāi)發(fā)者減少在 XML 編寫大量重復(fù)的 CRUD 代碼,這點(diǎn)是非常重要的 nice 的监婶。很顯然旅赢,Lambda 表達(dá)式對(duì)于提高程序員的開(kāi)發(fā)效率是不言而喻的,我想這點(diǎn)也是我作為程序員非常喜歡 Mybatis-Plus 的一個(gè)重要原因压储。
微信搜公眾號(hào)「猿芯」鲜漩,后臺(tái)私信回復(fù) 1024 免費(fèi)領(lǐng)取 SpringCloud、SpringBoot集惋,微信小程序孕似、Java面試、數(shù)據(jù)結(jié)構(gòu)刮刑、算法等全套視頻資料喉祭。
但是养渴,如果涉及對(duì)于多表之間的關(guān)聯(lián)查詢,lambda 表達(dá)式就顯得力不從心了泛烙,因?yàn)?Mybatis-Plus 并沒(méi)有提供類似于 join 查詢的條件構(gòu)造器理卑。
lambda 表達(dá)式優(yōu)點(diǎn):
- 單表操作,代碼非常簡(jiǎn)潔蔽氨,真正做到零配置藐唠,如不需要在 xml 或用注解(@Select)寫大量原生 SQL 代碼
- 并行計(jì)算
- 預(yù)測(cè)代表未來(lái)的編程趨勢(shì)
lambda 表達(dá)式缺點(diǎn):
- 單表操作,對(duì)于多表關(guān)聯(lián)查詢支持不好
- 調(diào)試?yán)щy
- 底層邏輯復(fù)雜
五鹉究、總結(jié)
Mybatis-Plus 推出的 lambda 表達(dá)式致力于構(gòu)建復(fù)雜的 where 查詢構(gòu)造器式并不是銀彈宇立,它可以解決你實(shí)際項(xiàng)目中 80% 的開(kāi)發(fā)效率問(wèn)題,但是針對(duì)一些復(fù)雜的大 SQL 查詢條件支持地并不好自赔,例如一些復(fù)雜的 SQL 報(bào)表統(tǒng)計(jì)查詢妈嘹。
所以,筆者推薦單表操作用 lambda 表達(dá)式绍妨,查詢推薦用 LambdaQueryWrapper润脸,更新用 LambdaUpdateWrapper;多表操作還是老老實(shí)實(shí)寫一些原生 SQL 他去,至于原生 SQL 寫在哪里毙驯? Mapper 文件或者基于注解,如 @Select 都是可以的孤页。
參考
- https://mp.baomidou.com/guide/wrapper.html
- http://www.reibang.com/p/613a6118e2e0
- https://blog.csdn.net/Solitude_w/article/details/108235236
- https://blog.csdn.net/weixin_44472810/article/details/105649901
- https://blog.csdn.net/weixin_44495678/article/details/106748214
作者:猿芯
本文首發(fā)于公眾號(hào):Java版web項(xiàng)目尔苦,歡迎關(guān)注獲取更多精彩內(nèi)容