国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

龍華哪有做網(wǎng)站設(shè)計網(wǎng)站關(guān)鍵詞優(yōu)化排名推薦

龍華哪有做網(wǎng)站設(shè)計,網(wǎng)站關(guān)鍵詞優(yōu)化排名推薦,專門做微信公眾號的網(wǎng)站,現(xiàn)成的ppt大全介紹 j?????MyBatis-Plus (opens new window)(簡稱 MP)是一個 MyBatis (opens new window)的增強工具,在 MyBatis 的基礎(chǔ)上只做增強不做改變,為簡化開發(fā)、提高效率而生。 官網(wǎng):MyBatis-Plus (baomidou.com) 1.…

介紹?

j?????MyBatis-Plus?(opens new window)(簡稱 MP)是一個?MyBatis?(opens new window)的增強工具,在 MyBatis 的基礎(chǔ)上只做增強不做改變,為簡化開發(fā)、提高效率而生。

官網(wǎng):MyBatis-Plus (baomidou.com)

1.引入MybatisPlus的起步依賴


MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且實現(xiàn)了自動裝配效果。
因此我們可以用MybatisPlus的starter代替Mybatis的starter:

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</dependency>

2.定義Mapper
?

為了簡化單表CRUD,MybatisPlus提供了一個基礎(chǔ)的BaseMapper接口,其中已經(jīng)實現(xiàn)了單表的CRUD:

因此我們自定義的Mapper只要實現(xiàn)了這個BaseMapper,就無需自己實現(xiàn)單表CRUD了。 修改mp-demo中的com.itheima.mp.mapper包下的UserMapper接口,讓其繼承BaseMapper

代碼如下:

package com.itheima.mp.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;public interface UserMapper extends BaseMapper<User> {
}

MybatisPlus就是根據(jù)PO實體的信息來推斷出表的信息,從而生成SQL的。默認(rèn)情況下:

  • MybatisPlus會把PO實體的類名駝峰轉(zhuǎn)下劃線作為表名

  • MybatisPlus會把PO實體的所有變量名駝峰轉(zhuǎn)下劃線作為表的字段名,并根據(jù)變量類型推斷字段類型

  • MybatisPlus會把名為id的字段作為主鍵

但很多情況下,默認(rèn)的實現(xiàn)與實際場景不符,因此MybatisPlus提供了一些注解便于我們聲明表信息。

常見注解

@TableName

說明:

  • 描述:表名注解,標(biāo)識實體類對應(yīng)的表

  • 使用位置:實體類

示例:

@TableName("user")
public class User {private Long id;private String name;
}

TableName注解除了指定表名以外,還可以指定很多其它屬性:

屬性

類型

必須指定

默認(rèn)值

描述

value

String

""

表名

schema

String

""

schema

keepGlobalPrefix

boolean

false

是否保持使用全局的 tablePrefix 的值(當(dāng)全局 tablePrefix 生效時)

resultMap

String

""

xml 中 resultMap 的 id(用于滿足特定類型的實體類對象綁定)

autoResultMap

boolean

false

是否自動構(gòu)建 resultMap 并使用(如果設(shè)置 resultMap 則不會進(jìn)行 resultMap 的自動構(gòu)建與注入)

excludeProperty

String[]

{}

需要排除的屬性名 @since 3.3.1

@TableId

說明:

  • 描述:主鍵注解,標(biāo)識實體類中的主鍵字段

  • 使用位置:實體類的主鍵字段

示例:

 
@TableName("user")
public class User {@TableIdprivate Long id;private String name;
}

TableId注解支持兩個屬性:

屬性

類型

必須指定

默認(rèn)值

描述

value

String

""

表名

type

Enum

IdType.NONE

指定主鍵類型

IdType支持的類型有:

描述

AUTO

數(shù)據(jù)庫 ID 自增

NONE

無狀態(tài),該類型為未設(shè)置主鍵類型(注解里等于跟隨全局,全局里約等于 INPUT)

INPUT

insert 前自行 set 主鍵值

ASSIGN_ID

分配 ID(主鍵類型為 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默認(rèn)實現(xiàn)類為DefaultIdentifierGenerator雪花算法)

ASSIGN_UUID

分配 UUID,主鍵類型為 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默認(rèn) default 方法)

ID_WORKER

分布式全局唯一 ID 長整型類型(please use ASSIGN_ID)

UUID

32 位 UUID 字符串(please use ASSIGN_UUID)

ID_WORKER_STR

分布式全局唯一 ID 字符串類型(please use ASSIGN_ID)

這里比較常見的有三種:

  • AUTO:利用數(shù)據(jù)庫的id自增長

  • INPUT:手動生成id

  • ASSIGN_ID:雪花算法生成Long類型的全局唯一id,這是默認(rèn)的ID策略

@TableField

說明:

描述:普通字段注解

示例:

 
@TableName("user")
public class User {@TableIdprivate Long id;private String name;private Integer age;@TableField("isMarried")private Boolean isMarried;@TableField("concat")private String concat;
}

一般情況下我們并不需要給字段添加@TableField注解,一些特殊情況除外:

  • 成員變量名與數(shù)據(jù)庫字段名不一致

  • 成員變量是以isXXX命名,按照JavaBean的規(guī)范,MybatisPlus識別字段時會把is去除,這就導(dǎo)致與數(shù)據(jù)庫不符。

  • 成員變量名與數(shù)據(jù)庫一致,但是與數(shù)據(jù)庫的關(guān)鍵字沖突。使用@TableField注解給字段名添加轉(zhuǎn)義字符:``

支持的其它屬性如下:

屬性

類型

必填

默認(rèn)值

描述

value

String

""

數(shù)據(jù)庫字段名

exist

boolean

true

是否為數(shù)據(jù)庫表字段

condition

String

""

字段 where 實體查詢比較條件,有值設(shè)置則按設(shè)置的值為準(zhǔn),沒有則為默認(rèn)全局的 %s=#{%s},參考(opens new window)

update

String

""

字段 update set 部分注入,例如:當(dāng)在version字段上注解update="%s+1" 表示更新時會 set version=version+1 (該屬性優(yōu)先級高于 el 屬性)

insertStrategy

Enum

FieldStrategy.DEFAULT

舉例:NOT_NULL insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)

updateStrategy

Enum

FieldStrategy.DEFAULT

舉例:IGNORED update table_a set column=#{columnProperty}

whereStrategy

Enum

FieldStrategy.DEFAULT

舉例:NOT_EMPTY where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>

fill

Enum

FieldFill.DEFAULT

字段自動填充策略

select

boolean

true

是否進(jìn)行 select 查詢

keepGlobalFormat

boolean

false

是否保持使用全局的 format 進(jìn)行處理

jdbcType

JdbcType

JdbcType.UNDEFINED

JDBC 類型 (該默認(rèn)值不代表會按照該值生效)

typeHandler

TypeHander

類型處理器 (該默認(rèn)值不代表會按照該值生效)

numericScale

String

""

指定小數(shù)點后保留的位數(shù)

MybatisPlus是如何獲取實現(xiàn)CRUD的數(shù)據(jù)庫表信息的?
默認(rèn)以類名駝峰轉(zhuǎn)下劃線作為表名
默認(rèn)把名為id的字段作為主鍵
默認(rèn)把變量名駝峰轉(zhuǎn)下劃線作為表的字段名MybatisPlus的常用注解有哪些?@TableName:指定表名稱及全局配置@Tableld:指定id字段及相關(guān)配置@TableField:指定普通字段及相關(guān)配置
IdType的常見類型有哪些?AUTO、ASSIGN ID、INPUT使用@TableField的常見場景是?
成員變量名與數(shù)據(jù)庫字段名不一致
成員變量名以is開頭,且是布爾值
成員變量名與數(shù)據(jù)庫關(guān)鍵字沖突
成員變量不是數(shù)據(jù)庫字段?

常見配置

MybatisPlus也支持基于yaml文件的自定義配置,詳見官方文檔:

使用配置 | MyBatis-Plus (baomidou.com)

大多數(shù)的配置都有默認(rèn)值,因此我們都無需配置。但還有一些是沒有默認(rèn)值的,例如:

  • 實體類的別名掃描包

  • 全局id類型

    mybatis-plus:type-aliases-package: com.itheima.mp.domain.poglobal-config:db-config:id-type: auto # 全局id類型為自增長

    需要注意的是,MyBatisPlus也支持手寫SQL的,而mapper文件的讀取地址可以自己配置:

mybatis-plus:mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,當(dāng)前這個是默認(rèn)值。

可以看到默認(rèn)值是classpath*:/mapper/**/*.xml,也就是說我們只要把mapper.xml文件放置這個目錄下就一定會被加載。

核心功能

剛才的案例中都是以id為條件的簡單CRUD,一些復(fù)雜條件的SQL語句就要用到一些更高級的功能了。

1.條件構(gòu)造器

除了新增以外,修改、刪除、查詢的SQL語句都需要指定where條件。因此BaseMapper中提供的相關(guān)方法除了以id作為where條件以外,還支持更加復(fù)雜的where條件。

參數(shù)中的Wrapper就是條件構(gòu)造的抽象類,其下有很多默認(rèn)實現(xiàn),繼承關(guān)系如圖:?

?

Wrapper的子類AbstractWrapper提供了where中包含的所有條件構(gòu)造方法:?

而QueryWrapper在AbstractWrapper的基礎(chǔ)上拓展了一個select方法,允許指定查詢字段:

而UpdateWrapper在AbstractWrapper的基礎(chǔ)上拓展了一個set方法,允許指定SQL中的SET部分:

QueryWrapper

無論是修改、刪除、查詢,都可以使用QueryWrapper來構(gòu)建查詢條件。接下來看一些例子: 查詢:查詢出名字中帶o的,存款大于等于1000元的人。代碼如下:

@Test
void testQueryWrapper() {// 1.構(gòu)建查詢條件 where name like "%o%" AND balance >= 1000QueryWrapper<User> wrapper = new QueryWrapper<User>().select("id", "username", "info", "balance").like("username", "o").ge("balance", 1000);// 2.查詢數(shù)據(jù)List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

?更新:更新用戶名為jack的用戶的余額為2000,代碼如下:

@Test
void testUpdateByQueryWrapper() {// 1.構(gòu)建查詢條件 where name = "Jack"QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");// 2.更新數(shù)據(jù),user中非null字段都會作為set語句User user = new User();user.setBalance(2000);userMapper.update(user, wrapper);
}

UpdateWrapper

基于BaseMapper中的update方法更新時只能直接賦值,對于一些復(fù)雜的需求就難以實現(xiàn)。 例如:更新id為1,2,4的用戶的余額,扣200,對應(yīng)的SQL應(yīng)該是:

UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)

SET的賦值結(jié)果是基于字段現(xiàn)有值的,這個時候就要利用UpdateWrapper中的setSql功能了:?

@Test
void testUpdateWrapper() {List<Long> ids = List.of(1L, 2L, 4L);// 1.生成SQLUpdateWrapper<User> wrapper = new UpdateWrapper<User>().setSql("balance = balance - 200") // SET balance = balance - 200.in("id", ids); // WHERE id in (1, 2, 4)// 2.更新,注意第一個參數(shù)可以給null,也就是不填更新字段和數(shù)據(jù),// 而是基于UpdateWrapper中的setSQL來更新userMapper.update(null, wrapper);
}

LambdaQueryWrapper

無論是QueryWrapper還是UpdateWrapper在構(gòu)造條件的時候都需要寫死字段名稱,會出現(xiàn)字符串魔法值。這在編程規(guī)范中顯然是不推薦的。 那怎么樣才能不寫字段名,又能知道字段名呢?

其中一種辦法是基于變量的gettter方法結(jié)合反射技術(shù)。因此我們只要將條件對應(yīng)的字段的getter方法傳遞給MybatisPlus,它就能計算出對應(yīng)的變量名了。而傳遞方法可以使用JDK8中的方法引用Lambda表達(dá)式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含兩個:

  • LambdaQueryWrapper

  • LambdaUpdateWrapper

分別對應(yīng)QueryWrapper和UpdateWrapper

其使用方式如下:

@Test
void testLambdaQueryWrapper() {// 1.構(gòu)建條件 WHERE username LIKE "%o%" AND balance >= 1000QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.lambda().select(User::getId, User::getUsername, User::getInfo, User::getBalance).like(User::getUsername, "o").ge(User::getBalance, 1000);// 2.查詢List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

2.自定義SQL

在演示UpdateWrapper的案例中,我們在代碼中編寫了更新的SQL語句:

這種寫法在某些企業(yè)也是不允許的,因為SQL語句最好都維護(hù)在持久層,而不是業(yè)務(wù)層。就當(dāng)前案例來說,由于條件是in語句,只能將SQL寫在Mapper.xml文件,利用foreach來生成動態(tài)SQL。 這實在是太麻煩了。假如查詢條件更復(fù)雜,動態(tài)SQL的編寫也會更加復(fù)雜。

所以,MybatisPlus提供了自定義SQL功能,可以讓我們利用Wrapper生成查詢條件,再結(jié)合Mapper.xml編寫SQL

基本用法

以當(dāng)前案例來說,我們可以這樣寫:

@Test
void testCustomWrapper() {// 1.準(zhǔn)備自定義查詢條件List<Long> ids = List.of(1L, 2L, 4L);QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);// 2.調(diào)用mapper的自定義方法,直接傳遞WrapperuserMapper.deductBalanceByIds(200, wrapper);
}

然后在UserMapper中自定義SQL:

package com.itheima.mp.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.Param;public interface UserMapper extends BaseMapper<User> {@Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

?這樣就省去了編寫復(fù)雜查詢條件的煩惱了。

多表關(guān)聯(lián)

理論上來講MyBatisPlus是不支持多表查詢的,不過我們可以利用Wrapper中自定義條件結(jié)合自定義SQL來實現(xiàn)多表查詢的效果。 例如,我們要查詢出所有收貨地址在北京的并且用戶id在1、2、4之中的用戶 要是自己基于mybatis實現(xiàn)SQL,大概是這樣的:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">SELECT *FROM user uINNER JOIN address a ON u.id = a.user_idWHERE u.id<foreach collection="ids" separator="," item="id" open="IN (" close=")">#{id}</foreach>AND a.city = #{city}</select>

可以看出其中最復(fù)雜的就是WHERE條件的編寫,如果業(yè)務(wù)復(fù)雜一些,這里的SQL會更變態(tài)。

但是基于自定義SQL結(jié)合Wrapper的玩法,我們就可以利用Wrapper來構(gòu)建查詢條件,然后手寫SELECT及FROM部分,實現(xiàn)多表查詢。

查詢條件這樣來構(gòu)建:

@Test
void testCustomJoinWrapper() {// 1.準(zhǔn)備自定義查詢條件QueryWrapper<User> wrapper = new QueryWrapper<User>().in("u.id", List.of(1L, 2L, 4L)).eq("a.city", "北京");// 2.調(diào)用mapper的自定義方法List<User> users = userMapper.queryUserByWrapper(wrapper);users.forEach(System.out::println);
}

然后在UserMapper中自定義方法:

@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);

當(dāng)然,也可以在UserMapper.xml中寫SQL:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}
</select>

Service接口

MybatisPlus不僅提供了BaseMapper,還提供了通用的Service接口及默認(rèn)實現(xiàn),封裝了一些常用的service模板方法。 通用接口為IService,默認(rèn)實現(xiàn)為ServiceImpl,其中封裝的方法可以分為以下幾類:

  • save:新增

  • remove:刪除

  • update:更新

  • get:查詢單個結(jié)果

  • list:查詢集合結(jié)果

  • count:計數(shù)

  • page:分頁查詢

CRUD

我們先倆看下基本的CRUD接口。 新增

  • save是新增單個元素

  • saveBatch是批量新增

  • saveOrUpdate是根據(jù)id判斷,如果數(shù)據(jù)存在就更新,不存在則新增

  • saveOrUpdateBatch是批量的新增或修改

?刪除:

  • removeById:根據(jù)id刪除

  • removeByIds:根據(jù)id批量刪除

  • removeByMap:根據(jù)Map中的鍵值對為條件刪除

  • remove(Wrapper<T>):根據(jù)Wrapper條件刪除

  • ~~removeBatchByIds~~:暫不支持

修改:

  • updateById:根據(jù)id修改

  • update(Wrapper<T>):根據(jù)UpdateWrapper修改,Wrapper中包含setwhere部分

  • update(T,Wrapper<T>):按照T內(nèi)的數(shù)據(jù)修改與Wrapper匹配到的數(shù)據(jù)

  • updateBatchById:根據(jù)id批量修改

Get:

?

  • getById:根據(jù)id查詢1條數(shù)據(jù)

  • getOne(Wrapper<T>):根據(jù)Wrapper查詢1條數(shù)據(jù)

  • getBaseMapper:獲取Service內(nèi)的BaseMapper實現(xiàn),某些時候需要直接調(diào)用Mapper內(nèi)的自定義SQL時可以用這個方法獲取到Mapper

List:

  • listByIds:根據(jù)id批量查詢

  • list(Wrapper<T>):根據(jù)Wrapper條件查詢多條數(shù)據(jù)

  • list():查詢所有

Count:

  • count():統(tǒng)計所有數(shù)量

  • count(Wrapper<T>):統(tǒng)計符合Wrapper條件的數(shù)據(jù)數(shù)量

getBaseMapper: 當(dāng)我們在service中要調(diào)用Mapper中自定義SQL時,就必須獲取service對應(yīng)的Mapper,就可以通過這個方法:

?

基本用法

由于Service中經(jīng)常需要定義與業(yè)務(wù)有關(guān)的自定義方法,因此我們不能直接使用IService,而是自定義Service接口,然后繼承IService以拓展方法。同時,讓自定義的Service實現(xiàn)類繼承ServiceImpl,這樣就不用自己實現(xiàn)IService中的接口了。

首先,定義IUserService,繼承IService

package com.itheima.mp.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;public interface IUserService extends IService<User> {// 拓展自定義方法
}

然后,編寫UserServiceImpl類,繼承ServiceImpl,實現(xiàn)UserService:?

package com.itheima.mp.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.service.IUserService;
import com.itheima.mp.mapper.UserMapper;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>implements IUserService {
}

項目結(jié)構(gòu)如下:?

?

接下來,我們快速實現(xiàn)下面4個接口:

編號

接口

請求方式

請求路徑

請求參數(shù)

返回值

1

新增用戶

POST

/users

用戶表單實體

2

刪除用戶

DELETE

/users/{id}

用戶id

3

根據(jù)id查詢用戶

GET

/users/{id}

用戶id

用戶VO

4

根據(jù)id批量查詢

GET

/users

用戶id集合

用戶VO集合

首先,我們在項目中引入幾個依賴:

<!--swagger-->
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId><version>4.1.0</version>
</dependency>
<!--web-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

然后需要配置swagger信息:

knife4j:enable: trueopenapi:title: 用戶管理接口文檔description: "用戶管理接口文檔"email: zhanghuyi@itcast.cnconcat: 虎哥url: https://www.itcast.cnversion: v1.0.0group:default:group-name: defaultapi-rule: packageapi-rule-resources:- com.itheima.mp.controller

然后,接口需要兩個實體:

  • UserFormDTO:代表新增時的用戶表單

  • UserVO:代表查詢的返回結(jié)果

首先是UserFormDTO:

package com.itheima.mp.domain.dto;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel(description = "用戶表單實體")
public class UserFormDTO {@ApiModelProperty("id")private Long id;@ApiModelProperty("用戶名")private String username;@ApiModelProperty("密碼")private String password;@ApiModelProperty("注冊手機號")private String phone;@ApiModelProperty("詳細(xì)信息,JSON風(fēng)格")private String info;@ApiModelProperty("賬戶余額")private Integer balance;
}

然后是UserVO:

package com.itheima.mp.domain.vo;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel(description = "用戶VO實體")
public class UserVO {@ApiModelProperty("用戶id")private Long id;@ApiModelProperty("用戶名")private String username;@ApiModelProperty("詳細(xì)信息")private String info;@ApiModelProperty("使用狀態(tài)(1正常 2凍結(jié))")private Integer status;@ApiModelProperty("賬戶余額")private Integer balance;
}

?最后,按照Restful風(fēng)格編寫Controller接口方法:

package com.itheima.mp.controller;import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;import java.util.List;@Api(tags = "用戶管理接口")
@RequiredArgsConstructor
@RestController
@RequestMapping("users")
public class UserController {private final IUserService userService;@PostMapping@ApiOperation("新增用戶")public void saveUser(@RequestBody UserFormDTO userFormDTO){// 1.轉(zhuǎn)換DTO為POUser user = BeanUtil.copyProperties(userFormDTO, User.class);// 2.新增userService.save(user);}@DeleteMapping("/{id}")@ApiOperation("刪除用戶")public void removeUserById(@PathVariable("id") Long userId){userService.removeById(userId);}@GetMapping("/{id}")@ApiOperation("根據(jù)id查詢用戶")public UserVO queryUserById(@PathVariable("id") Long userId){// 1.查詢用戶User user = userService.getById(userId);// 2.處理voreturn BeanUtil.copyProperties(user, UserVO.class);}@GetMapping@ApiOperation("根據(jù)id集合查詢用戶")public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids){// 1.查詢用戶List<User> users = userService.listByIds(ids);// 2.處理voreturn BeanUtil.copyToList(users, UserVO.class);}
}

可以看到上述接口都直接在controller即可實現(xiàn),無需編寫任何service代碼,非常方便。

不過,一些帶有業(yè)務(wù)邏輯的接口則需要在service中自定義實現(xiàn)了。例如下面的需求:

  • 根據(jù)id扣減用戶余額

這看起來是個簡單修改功能,只要修改用戶余額即可。但這個業(yè)務(wù)包含一些業(yè)務(wù)邏輯處理:

  • 判斷用戶狀態(tài)是否正常

  • 判斷用戶余額是否充足

這些業(yè)務(wù)邏輯都要在service層來做,另外更新余額需要自定義SQL,要在mapper中來實現(xiàn)。因此,我們除了要編寫controller以外,具體的業(yè)務(wù)還要在service和mapper中編寫。

首先在UserController中定義一個方法:

@PutMapping("{id}/deduction/{money}")
@ApiOperation("扣減用戶余額")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){userService.deductBalance(id, money);
}

然后是UserService接口:

package com.itheima.mp.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;public interface IUserService extends IService<User> {void deductBalance(Long id, Integer money);
}

最后是UserServiceImpl實現(xiàn)類:?

package com.itheima.mp.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.mapper.UserMapper;
import com.itheima.mp.service.IUserService;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic void deductBalance(Long id, Integer money) {// 1.查詢用戶User user = getById(id);// 2.判斷用戶狀態(tài)if (user == null || user.getStatus() == 2) {throw new RuntimeException("用戶狀態(tài)異常");}// 3.判斷用戶余額if (user.getBalance() < money) {throw new RuntimeException("用戶余額不足");}// 4.扣減余額baseMapper.deductMoneyById(id, money);}
}

最后是mapper:

@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);

Lambda

IService中還提供了Lambda功能來簡化我們的復(fù)雜查詢及更新功能。我們通過兩個案例來學(xué)習(xí)一下。

案例一:實現(xiàn)一個根據(jù)復(fù)雜條件查詢用戶的接口,查詢條件如下:

  • name:用戶名關(guān)鍵字,可以為空

  • status:用戶狀態(tài),可以為空

  • minBalance:最小余額,可以為空

  • maxBalance:最大余額,可以為空

可以理解成一個用戶的后臺管理界面,管理員可以自己選擇條件來篩選用戶,因此上述條件不一定存在,需要做判斷。

我們首先需要定義一個查詢條件實體,UserQuery實體:

package com.itheima.mp.domain.query;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel(description = "用戶查詢條件實體")
public class UserQuery {@ApiModelProperty("用戶名關(guān)鍵字")private String name;@ApiModelProperty("用戶狀態(tài):1-正常,2-凍結(jié)")private Integer status;@ApiModelProperty("余額最小值")private Integer minBalance;@ApiModelProperty("余額最大值")private Integer maxBalance;
}

?接下來我們在UserController中定義一個controller方法:

@GetMapping("/list")
@ApiOperation("根據(jù)id集合查詢用戶")
public List<UserVO> queryUsers(UserQuery query){// 1.組織條件String username = query.getName();Integer status = query.getStatus();Integer minBalance = query.getMinBalance();Integer maxBalance = query.getMaxBalance();LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda().like(username != null, User::getUsername, username).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance);// 2.查詢用戶List<User> users = userService.list(wrapper);// 3.處理voreturn BeanUtil.copyToList(users, UserVO.class);
}

在組織查詢條件的時候,我們加入了 username != null 這樣的參數(shù),意思就是當(dāng)條件成立時才會添加這個查詢條件,類似Mybatis的mapper.xml文件中的<if>標(biāo)簽。這樣就實現(xiàn)了動態(tài)查詢條件效果了。

不過,上述條件構(gòu)建的代碼太麻煩了。 因此Service中對LambdaQueryWrapperLambdaUpdateWrapper的用法進(jìn)一步做了簡化。我們無需自己通過new的方式來創(chuàng)建Wrapper,而是直接調(diào)用lambdaQuerylambdaUpdate方法:

基于Lambda查詢:

@GetMapping("/list")
@ApiOperation("根據(jù)id集合查詢用戶")
public List<UserVO> queryUsers(UserQuery query){// 1.組織條件String username = query.getName();Integer status = query.getStatus();Integer minBalance = query.getMinBalance();Integer maxBalance = query.getMaxBalance();// 2.查詢用戶List<User> users = userService.lambdaQuery().like(username != null, User::getUsername, username).eq(status != null, User::getStatus, status).ge(minBalance != null, User::getBalance, minBalance).le(maxBalance != null, User::getBalance, maxBalance).list();// 3.處理voreturn BeanUtil.copyToList(users, UserVO.class);
}

可以發(fā)現(xiàn)lambdaQuery方法中除了可以構(gòu)建條件,還需要在鏈?zhǔn)骄幊痰淖詈筇砑右粋€list(),這是在告訴MP我們的調(diào)用結(jié)果需要是一個list集合。這里不僅可以用list(),可選的方法有:

  • .one():最多1個結(jié)果

  • .list():返回集合結(jié)果

  • .count():返回計數(shù)結(jié)果

MybatisPlus會根據(jù)鏈?zhǔn)骄幊痰淖詈笠粋€方法來判斷最終的返回結(jié)果。

與lambdaQuery方法類似,IService中的lambdaUpdate方法可以非常方便的實現(xiàn)復(fù)雜更新業(yè)務(wù)。

例如下面的需求:

需求:改造根據(jù)id修改用戶余額的接口,要求如下

  • 如果扣減后余額為0,則將用戶status修改為凍結(jié)狀態(tài)(2)

也就是說我們在扣減用戶余額時,需要對用戶剩余余額做出判斷,如果發(fā)現(xiàn)剩余余額為0,則應(yīng)該將status修改為2,這就是說update語句的set部分是動態(tài)的。

實現(xiàn)如下:

@Override
@Transactional
public void deductBalance(Long id, Integer money) {// 1.查詢用戶User user = getById(id);// 2.校驗用戶狀態(tài)if (user == null || user.getStatus() == 2) {throw new RuntimeException("用戶狀態(tài)異常!");}// 3.校驗余額是否充足if (user.getBalance() < money) {throw new RuntimeException("用戶余額不足!");}// 4.扣減余額 update tb_user set balance = balance - ?int remainBalance = user.getBalance() - money;lambdaUpdate().set(User::getBalance, remainBalance) // 更新余額.set(remainBalance == 0, User::getStatus, 2) // 動態(tài)判斷,是否更新status.eq(User::getId, id).eq(User::getBalance, user.getBalance()) // 樂觀鎖.update();
}

批量新增

IService中的批量新增功能使用起來非常方便,但有一點注意事項,我們先來測試一下。 首先我們測試逐條插入數(shù)據(jù):

@Test
void testSaveOneByOne() {long b = System.currentTimeMillis();for (int i = 1; i <= 100000; i++) {userService.save(buildUser(i));}long e = System.currentTimeMillis();System.out.println("耗時:" + (e - b));
}private User buildUser(int i) {User user = new User();user.setUsername("user_" + i);user.setPassword("123");user.setPhone("" + (18688190000L + i));user.setBalance(2000);user.setInfo("{\"age\": 24, \"intro\": \"英文老師\", \"gender\": \"female\"}");user.setCreateTime(LocalDateTime.now());user.setUpdateTime(user.getCreateTime());return user;
}

執(zhí)行結(jié)果如下:?

可以看到速度非常慢。

然后再試試MybatisPlus的批處理:

@Test
void testSaveBatch() {// 準(zhǔn)備10萬條數(shù)據(jù)List<User> list = new ArrayList<>(1000);long b = System.currentTimeMillis();for (int i = 1; i <= 100000; i++) {list.add(buildUser(i));// 每1000條批量插入一次if (i % 1000 == 0) {userService.saveBatch(list);list.clear();}}long e = System.currentTimeMillis();System.out.println("耗時:" + (e - b));
}

執(zhí)行最終耗時如下:?

?

可以看到使用了批處理以后,比逐條新增效率提高了10倍左右,性能還是不錯的。

不過,我們簡單查看一下MybatisPlus源碼:

@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}
// ...SqlHelper
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {Assert.isFalse(batchSize < 1, "batchSize must not be less than one");return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {int size = list.size();int idxLimit = Math.min(batchSize, size);int i = 1;for (E element : list) {consumer.accept(sqlSession, element);if (i == idxLimit) {sqlSession.flushStatements();idxLimit = Math.min(idxLimit + batchSize, size);}i++;}});
}

?可以發(fā)現(xiàn)其實MybatisPlus的批處理是基于PrepareStatement的預(yù)編譯模式,然后批量提交,最終在數(shù)據(jù)庫執(zhí)行時還是會有多條insert語句,逐條插入數(shù)據(jù)。SQL類似這樣:

Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
Parameters: user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01

而如果想要得到最佳性能,最好是將多條SQL合并為一條,像這樣:?

INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES 
(user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);

該怎么做呢?

MySQL的客戶端連接參數(shù)中有這樣的一個參數(shù):rewriteBatchedStatements。顧名思義,就是重寫批處理的statement語句。參考文檔:

https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-performance-extensions.html#cj-conn-prop_rewriteBatchedStatements

這個參數(shù)的默認(rèn)值是false,我們需要修改連接參數(shù),將其配置為true

修改項目中的application.yml文件,在jdbc的url后面添加參數(shù)&rewriteBatchedStatements=true:

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=truedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: MySQL123

再次測試插入10萬條數(shù)據(jù),可以發(fā)現(xiàn)速度有非常明顯的提升:?

ClientPreparedStatementexecuteBatchInternal中,有判斷rewriteBatchedStatements值是否為true并重寫SQL的功能:

最終,SQL被重寫了:

擴(kuò)展功能

1.代碼生成

在使用MybatisPlus以后,基礎(chǔ)的Mapper、ServicePO代碼相對固定,重復(fù)編寫也比較麻煩。因此MybatisPlus官方提供了代碼生成器根據(jù)數(shù)據(jù)庫表結(jié)構(gòu)生成PO、MapperService等相關(guān)代碼。只不過代碼生成器同樣要編碼使用,也很麻煩。

這里推薦大家使用一款MybatisPlus的插件,它可以基于圖形化界面完成MybatisPlus的代碼生成,非常簡單。

安裝插件

Idea的plugins市場中搜索并安裝MyBatisPlus插件:

?

然后重啟你的Idea即可使用。

使用

剛好數(shù)據(jù)庫中還有一張address表尚未生成對應(yīng)的實體和mapper等基礎(chǔ)代碼。我們利用插件生成一下。 首先需要配置數(shù)據(jù)庫地址,在Idea頂部菜單中,找到other,選擇Config Database

在彈出的窗口中填寫數(shù)據(jù)庫連接的基本信息:

點擊OK保存。

然后再次點擊Idea頂部菜單中的other,然后選擇Code Generator:

?

在彈出的表單中填寫信息:?

最終,代碼自動生成到指定的位置了:?

2.靜態(tài)工具

有的時候Service之間也會相互調(diào)用,為了避免出現(xiàn)循環(huán)依賴問題,MybatisPlus提供一個靜態(tài)工具類:Db,其中的一些靜態(tài)方法與IService中方法簽名基本一致,也可以幫助我們實現(xiàn)CRUD功能:

?

示例:

@Test
void testDbGet() {User user = Db.getById(1L, User.class);System.out.println(user);
}@Test
void testDbList() {// 利用Db實現(xiàn)復(fù)雜條件查詢List<User> list = Db.lambdaQuery(User.class).like(User::getUsername, "o").ge(User::getBalance, 1000).list();list.forEach(System.out::println);
}@Test
void testDbUpdate() {Db.lambdaUpdate(User.class).set(User::getBalance, 2000).eq(User::getUsername, "Rose");
}

需求:改造根據(jù)id用戶查詢的接口,查詢用戶的同時返回用戶收貨地址列表

首先,我們要添加一個收貨地址的VO對象:

package com.itheima.mp.domain.vo;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel(description = "收貨地址VO")
public class AddressVO{@ApiModelProperty("id")private Long id;@ApiModelProperty("用戶ID")private Long userId;@ApiModelProperty("省")private String province;@ApiModelProperty("市")private String city;@ApiModelProperty("縣/區(qū)")private String town;@ApiModelProperty("手機")private String mobile;@ApiModelProperty("詳細(xì)地址")private String street;@ApiModelProperty("聯(lián)系人")private String contact;@ApiModelProperty("是否是默認(rèn) 1默認(rèn) 0否")private Boolean isDefault;@ApiModelProperty("備注")private String notes;
}

?接下來,修改UserController中根據(jù)id查詢用戶的業(yè)務(wù)接口:

@GetMapping("/{id}")
@ApiOperation("根據(jù)id查詢用戶")
public UserVO queryUserById(@PathVariable("id") Long userId){// 基于自定義service方法查詢return userService.queryUserAndAddressById(userId);
}

由于查詢業(yè)務(wù)復(fù)雜,所以要在service層來實現(xiàn)。首先在IUserService中定義方法:?

package com.itheima.mp.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;public interface IUserService extends IService<User> {void deduct(Long id, Integer money);UserVO queryUserAndAddressById(Long userId);
}

然后,在UserServiceImpl中實現(xiàn)該方法:

@Override
public UserVO queryUserAndAddressById(Long userId) {// 1.查詢用戶User user = getById(userId);if (user == null) {return null;}// 2.查詢收貨地址List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, userId).list();// 3.處理voUserVO userVO = BeanUtil.copyProperties(user, UserVO.class);userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));return userVO;
}

在查詢地址時,我們采用了Db的靜態(tài)方法,因此避免了注入AddressService,減少了循環(huán)依賴的風(fēng)險。

再來實現(xiàn)一個功能:

  • 根據(jù)id批量查詢用戶,并查詢出用戶對應(yīng)的所有地址

3.邏輯刪除

對于一些比較重要的數(shù)據(jù),我們往往會采用邏輯刪除的方案,即:

  • 在表中添加一個字段標(biāo)記數(shù)據(jù)是否被刪除

  • 當(dāng)刪除數(shù)據(jù)時把標(biāo)記置為true

  • 查詢時過濾掉標(biāo)記為true的數(shù)據(jù)

一旦采用了邏輯刪除,所有的查詢和刪除邏輯都要跟著變化,非常麻煩。

為了解決這個問題,MybatisPlus就添加了對邏輯刪除的支持。

注意,只有MybatisPlus生成的SQL語句才支持自動的邏輯刪除,自定義SQL需要自己手動處理邏輯刪除。

例如,我們給address表添加一個邏輯刪除字段:

alter table address add deleted bit default b'0' null comment '邏輯刪除';

然后給Address實體添加deleted字段:?

接下來,我們要在application.yml中配置邏輯刪除字段:?

mybatis-plus:global-config:db-config:logic-delete-field: deleted # 全局邏輯刪除的實體字段名(since 3.3.0,配置后可以忽略不配置步驟2)logic-delete-value: 1 # 邏輯已刪除值(默認(rèn)為 1)logic-not-delete-value: 0 # 邏輯未刪除值(默認(rèn)為 0)

測試: 首先,我們執(zhí)行一個刪除操作:?

@Test
void testDeleteByLogic() {// 刪除方法與以前沒有區(qū)別addressService.removeById(59L);
}

方法與普通刪除一模一樣,但是底層的SQL邏輯變了:

查詢一下試試:?

@Test
void testQuery() {List<Address> list = addressService.list();list.forEach(System.out::println);
}

會發(fā)現(xiàn)id為59的確實沒有查詢出來,而且SQL中也對邏輯刪除字段做了判斷:?

?

綜上, 開啟了邏輯刪除功能以后,我們就可以像普通刪除一樣做CRUD,基本不用考慮代碼邏輯問題。還是非常方便的。

注意: 邏輯刪除本身也有自己的問題,比如:

  • 會導(dǎo)致數(shù)據(jù)庫表垃圾數(shù)據(jù)越來越多,從而影響查詢效率

  • SQL中全都需要對邏輯刪除字段做判斷,影響查詢效率

因此,我不太推薦采用邏輯刪除功能,如果數(shù)據(jù)不能刪除,可以采用把數(shù)據(jù)遷移到其它表的辦法。

4.通用枚舉

User類中有一個用戶狀態(tài)字段:

這種字段我們一般會定義一個枚舉,做業(yè)務(wù)判斷的時候就可以直接基于枚舉做比較。但是我們數(shù)據(jù)庫采用的是int類型,對應(yīng)的PO也是Integer。因此業(yè)務(wù)操作時必須手動把枚舉Integer轉(zhuǎn)換,非常麻煩。

因此,MybatisPlus提供了一個處理枚舉的類型轉(zhuǎn)換器,可以幫我們把枚舉類型與數(shù)據(jù)庫類型自動轉(zhuǎn)換。

定義枚舉

我們定義一個用戶狀態(tài)的枚舉:

?

代碼如下:

package com.itheima.mp.enums;import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;@Getter
public enum UserStatus {NORMAL(1, "正常"),FREEZE(2, "凍結(jié)");private final int value;private final String desc;UserStatus(int value, String desc) {this.value = value;this.desc = desc;}
}

然后把User類中的status字段改為UserStatus 類型:

要讓MybatisPlus處理枚舉與數(shù)據(jù)庫類型自動轉(zhuǎn)換,我們必須告訴MybatisPlus,枚舉中的哪個字段的值作為數(shù)據(jù)庫值。 MybatisPlus提供了@EnumValue注解來標(biāo)記枚舉屬性:?

配置枚舉處理器

在application.yaml文件中添加配置:

mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

測試

@Test
void testService() {List<User> list = userService.list();list.forEach(System.out::println);
}

最終,查詢出的User類的status字段會是枚舉類型:?

?

?同時,為了使頁面查詢結(jié)果也是枚舉格式,我們需要修改UserVO中的status屬性:

并且,在UserStatus枚舉中通過@JsonValue注解標(biāo)記JSON序列化時展示的字段:?

最后,在頁面查詢,結(jié)果如下:?

5.JSON類型處理器

數(shù)據(jù)庫的user表中有一個info字段,是JSON類型:

?

格式像這樣:?

{"age": 20, "intro": "佛系青年", "gender": "male"}

而目前User實體類中卻是String類型:

這樣一來,我們要讀取info中的屬性時就非常不方便。如果要方便獲取,info的類型最好是一個Map或者實體類。

而一旦我們把info改為對象類型,就需要在寫入數(shù)據(jù)庫時手動轉(zhuǎn)為String,再讀取數(shù)據(jù)庫時,手動轉(zhuǎn)換為對象,這會非常麻煩。

因此MybatisPlus提供了很多特殊類型字段的類型處理器,解決特殊字段類型與數(shù)據(jù)庫類型轉(zhuǎn)換的問題。例如處理JSON就可以使用JacksonTypeHandler處理器。

接下來,我們就來看看這個處理器該如何使用。

定義實體

首先,我們定義一個單獨實體類來與info字段的屬性匹配:

代碼如下:

package com.itheima.mp.domain.po;import lombok.Data;@Data
public class UserInfo {private Integer age;private String intro;private String gender;
}

使用類型處理器

接下來,將User類的info字段修改為UserInfo類型,并聲明類型處理器:

測試可以發(fā)現(xiàn),所有數(shù)據(jù)都正確封裝到UserInfo當(dāng)中了:?

同時,為了讓頁面返回的結(jié)果也以對象格式返回,我們要修改UserVO中的info字段:

此時,在頁面查詢結(jié)果如下:?

插件功能

MybatisPlus提供了很多的插件功能,進(jìn)一步拓展其功能。目前已有的插件有:

  • PaginationInnerInterceptor:自動分頁

  • TenantLineInnerInterceptor:多租戶

  • DynamicTableNameInnerInterceptor:動態(tài)表名

  • OptimisticLockerInnerInterceptor:樂觀鎖

  • IllegalSQLInnerInterceptor:sql 性能規(guī)范

  • BlockAttackInnerInterceptor:防止全表更新與刪除

注意: 使用多個分頁插件的時候需要注意插件定義順序,建議使用順序如下:

  • 多租戶,動態(tài)表名

  • 分頁,樂觀鎖

  • sql 性能規(guī)范,防止全表更新與刪除

這里我們以分頁插件為里來學(xué)習(xí)插件的用法。

分頁插件

在未引入分頁插件的情況下,MybatisPlus是不支持分頁功能的,IServiceBaseMapper中的分頁方法都無法正常起效。 所以,我們必須配置分頁插件。

配置分頁插件

在項目中新建一個配置類:

其代碼如下:?

package com.itheima.mp.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MybatisConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {// 初始化核心插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分頁插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}

分頁API

編寫一個分頁查詢的測試:

@Test
void testPageQuery() {// 1.分頁查詢,new Page()的兩個參數(shù)分別是:頁碼、每頁大小Page<User> p = userService.page(new Page<>(2, 2));// 2.總條數(shù)System.out.println("total = " + p.getTotal());// 3.總頁數(shù)System.out.println("pages = " + p.getPages());// 4.數(shù)據(jù)List<User> records = p.getRecords();records.forEach(System.out::println);
}

運行的SQL如下:?

這里用到了分頁參數(shù),Page,即可以支持分頁參數(shù),也可以支持排序參數(shù)。常見的API如下:?

int pageNo = 1, pageSize = 5;
// 分頁參數(shù)
Page<User> page = Page.of(pageNo, pageSize);
// 排序參數(shù), 通過OrderItem來指定
page.addOrder(new OrderItem("balance", false));userService.page(page);

通用分頁實體

現(xiàn)在要實現(xiàn)一個用戶分頁查詢的接口,接口規(guī)范如下:

參數(shù)

說明

請求方式

GET

請求路徑

/users/page

請求參數(shù)

 

{ "pageNo": 1, "pageSize": 5, "sortBy": "balance", "isAsc": false, "name": "o", "status": 1 }

返回值

 

{ "total": 100006, "pages": 50003, "list": [ { "id": 1685100878975279298, "username": "user_9****", "info": { "age": 24, "intro": "英文老師", "gender": "female" }, "status": "正常", "balance": 2000 } ] }

特殊說明

  • 如果排序字段為空,默認(rèn)按照更新時間排序

  • 排序字段不為空,則按照排序字段排序

這里需要定義3個實體:

  • UserQuery:分頁查詢條件的實體,包含分頁、排序參數(shù)、過濾條件

  • PageDTO:分頁結(jié)果實體,包含總條數(shù)、總頁數(shù)、當(dāng)前頁數(shù)據(jù)

  • UserVO:用戶頁面視圖實體

實體

由于UserQuery之前已經(jīng)定義過了,并且其中已經(jīng)包含了過濾條件,具體代碼如下:

package com.itheima.mp.domain.query;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel(description = "用戶查詢條件實體")
public class UserQuery {@ApiModelProperty("用戶名關(guān)鍵字")private String name;@ApiModelProperty("用戶狀態(tài):1-正常,2-凍結(jié)")private Integer status;@ApiModelProperty("余額最小值")private Integer minBalance;@ApiModelProperty("余額最大值")private Integer maxBalance;
}

?其中缺少的僅僅是分頁條件,而分頁條件不僅僅用戶分頁查詢需要,以后其它業(yè)務(wù)也都有分頁查詢的需求。因此建議將分頁查詢條件單獨定義為一個PageQuery實體:

PageQuery是前端提交的查詢參數(shù),一般包含四個屬性:

  • pageNo:頁碼

  • pageSize:每頁數(shù)據(jù)條數(shù)

  • sortBy:排序字段

  • isAsc:是否升序

@Data
@ApiModel(description = "分頁查詢實體")
public class PageQuery {@ApiModelProperty("頁碼")private Long pageNo;@ApiModelProperty("頁碼")private Long pageSize;@ApiModelProperty("排序字段")private String sortBy;@ApiModelProperty("是否升序")private Boolean isAsc;
}

?然后,讓我們的UserQuery繼承這個實體:

package com.itheima.mp.domain.query;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "用戶查詢條件實體")
public class UserQuery extends PageQuery {@ApiModelProperty("用戶名關(guān)鍵字")private String name;@ApiModelProperty("用戶狀態(tài):1-正常,2-凍結(jié)")private Integer status;@ApiModelProperty("余額最小值")private Integer minBalance;@ApiModelProperty("余額最大值")private Integer maxBalance;
}

?返回值的用戶實體沿用之前定一個UserVO實體:

?最后,則是分頁實體PageDTO:

?

代碼如下:?

package com.itheima.mp.domain.dto;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.util.List;@Data
@ApiModel(description = "分頁結(jié)果")
public class PageDTO<T> {@ApiModelProperty("總條數(shù)")private Long total;@ApiModelProperty("總頁數(shù)")private Long pages;@ApiModelProperty("集合")private List<T> list;
}

開發(fā)接口

我們在UserController中定義分頁查詢用戶的接口:

package com.itheima.mp.controller;import com.itheima.mp.domain.dto.PageDTO;
import com.itheima.mp.domain.query.PageQuery;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("users")
@RequiredArgsConstructor
public class UserController {private final UserService userService;@GetMapping("/page")public PageDTO<UserVO> queryUsersPage(UserQuery query){return userService.queryUsersPage(query);}// 。。。 略
}

然后在IUserService中創(chuàng)建queryUsersPage方法:

PageDTO<UserVO> queryUsersPage(PageQuery query);

?接下來,在UserServiceImpl中實現(xiàn)該方法:

@Override
public PageDTO<UserVO> queryUsersPage(PageQuery query) {// 1.構(gòu)建條件// 1.1.分頁條件Page<User> page = Page.of(query.getPageNo(), query.getPageSize());// 1.2.排序條件if (query.getSortBy() != null) {page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));}else{// 默認(rèn)按照更新時間排序page.addOrder(new OrderItem("update_time", false));}// 2.查詢page(page);// 3.數(shù)據(jù)非空校驗List<User> records = page.getRecords();if (records == null || records.size() <= 0) {// 無數(shù)據(jù),返回空結(jié)果return new PageDTO<>(page.getTotal(), page.getPages(), Collections.emptyList());}// 4.有數(shù)據(jù),轉(zhuǎn)換List<UserVO> list = BeanUtil.copyToList(records, UserVO.class);// 5.封裝返回return new PageDTO<UserVO>(page.getTotal(), page.getPages(), list);
}

啟動項目,在頁面查看:?

改造PageQuery實體

在剛才的代碼中,從PageQueryMybatisPlusPage之間轉(zhuǎn)換的過程還是比較麻煩的。

我們完全可以在PageQuery這個實體中定義一個工具方法,簡化開發(fā)。 像這樣:

package com.itheima.mp.domain.query;import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;@Data
public class PageQuery {private Integer pageNo;private Integer pageSize;private String sortBy;private Boolean isAsc;public <T>  Page<T> toMpPage(OrderItem ... orders){// 1.分頁條件Page<T> p = Page.of(pageNo, pageSize);// 2.排序條件// 2.1.先看前端有沒有傳排序字段if (sortBy != null) {p.addOrder(new OrderItem(sortBy, isAsc));return p;}// 2.2.再看有沒有手動指定排序字段if(orders != null){p.addOrder(orders);}return p;}public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){return this.toMpPage(new OrderItem(defaultSortBy, isAsc));}public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {return toMpPage("create_time", false);}public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {return toMpPage("update_time", false);}
}

?這樣我們在開發(fā)也時就可以省去對從PageQueryPage的的轉(zhuǎn)換:

// 1.構(gòu)建條件
Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();

改造PageDTO實體

在查詢出分頁結(jié)果后,數(shù)據(jù)的非空校驗,數(shù)據(jù)的vo轉(zhuǎn)換都是模板代碼,編寫起來很麻煩。

我們完全可以將其封裝到PageDTO的工具方法中,簡化整個過程:

package com.itheima.mp.domain.dto;import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {private Long total;private Long pages;private List<V> list;/*** 返回空分頁結(jié)果* @param p MybatisPlus的分頁結(jié)果* @param <V> 目標(biāo)VO類型* @param <P> 原始PO類型* @return VO的分頁對象*/public static <V, P> PageDTO<V> empty(Page<P> p){return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());}/*** 將MybatisPlus分頁結(jié)果轉(zhuǎn)為 VO分頁結(jié)果* @param p MybatisPlus的分頁結(jié)果* @param voClass 目標(biāo)VO類型的字節(jié)碼* @param <V> 目標(biāo)VO類型* @param <P> 原始PO類型* @return VO的分頁對象*/public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {// 1.非空校驗List<P> records = p.getRecords();if (records == null || records.size() <= 0) {// 無數(shù)據(jù),返回空結(jié)果return empty(p);}// 2.數(shù)據(jù)轉(zhuǎn)換List<V> vos = BeanUtil.copyToList(records, voClass);// 3.封裝返回return new PageDTO<>(p.getTotal(), p.getPages(), vos);}/*** 將MybatisPlus分頁結(jié)果轉(zhuǎn)為 VO分頁結(jié)果,允許用戶自定義PO到VO的轉(zhuǎn)換方式* @param p MybatisPlus的分頁結(jié)果* @param convertor PO到VO的轉(zhuǎn)換函數(shù)* @param <V> 目標(biāo)VO類型* @param <P> 原始PO類型* @return VO的分頁對象*/public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {// 1.非空校驗List<P> records = p.getRecords();if (records == null || records.size() <= 0) {// 無數(shù)據(jù),返回空結(jié)果return empty(p);}// 2.數(shù)據(jù)轉(zhuǎn)換List<V> vos = records.stream().map(convertor).collect(Collectors.toList());// 3.封裝返回return new PageDTO<>(p.getTotal(), p.getPages(), vos);}
}

?最終,業(yè)務(wù)層的代碼可以簡化為:

@Override
public PageDTO<UserVO> queryUserByPage(PageQuery query) {// 1.構(gòu)建條件Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();// 2.查詢page(page);// 3.封裝返回return PageDTO.of(page, UserVO.class);
}

如果是希望自定義PO到VO的轉(zhuǎn)換過程,可以這樣做:

@Override
public PageDTO<UserVO> queryUserByPage(PageQuery query) {// 1.構(gòu)建條件Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();// 2.查詢page(page);// 3.封裝返回return PageDTO.of(page, user -> {// 拷貝屬性到VOUserVO vo = BeanUtil.copyProperties(user, UserVO.class);// 用戶名脫敏String username = vo.getUsername();vo.setUsername(username.substring(0, username.length() - 2) + "**");return vo;});
}

?最終查詢的結(jié)果如下:

http://m.aloenet.com.cn/news/32256.html

相關(guān)文章:

  • 蘭州關(guān)鍵詞優(yōu)化效果西安seo服務(wù)培訓(xùn)
  • wordpress技術(shù)類主題關(guān)鍵詞長尾詞優(yōu)化
  • 用什么做網(wǎng)站最簡單百度招商加盟
  • 建站工具官網(wǎng)小程序開發(fā)流程
  • 網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司品牌營銷推廣策劃公司
  • 華為云怎么做網(wǎng)站域名停靠網(wǎng)頁app推廣大全
  • 汽車app網(wǎng)站建設(shè)優(yōu)化關(guān)鍵詞規(guī)則
  • 《網(wǎng)站建設(shè)與維護(hù)》講義基本seo
  • 給別人做網(wǎng)站去掉版權(quán)谷歌瀏覽器2021最新版
  • seo網(wǎng)站托管人員優(yōu)化是什么意思
  • 公司網(wǎng)站建設(shè)應(yīng)注意什么seo技術(shù)網(wǎng)網(wǎng)
  • 網(wǎng)站建設(shè)完整seo外包公司哪家好
  • 山東 基層政府網(wǎng)站集約化建設(shè)免費自媒體網(wǎng)站
  • 做亞馬遜網(wǎng)站需要租辦公室嗎百度關(guān)鍵詞規(guī)劃師
  • 倒閉漢化組的wordpress網(wǎng)絡(luò)優(yōu)化工具app手機版
  • 找合伙人做紅木家具網(wǎng)站廈門seo百度快照優(yōu)化
  • 網(wǎng)頁設(shè)計軟件app百度seo入駐
  • 做網(wǎng)站v1認(rèn)證需要付費嗎seo外包服務(wù)公司
  • 電影網(wǎng)站app怎么做的怎么創(chuàng)建自己的免費網(wǎng)址
  • 優(yōu)化網(wǎng)站的網(wǎng)站seo快速優(yōu)化排名
  • 深圳效果好的免費網(wǎng)站建設(shè)seo知識總結(jié)
  • 邢臺路橋建設(shè)總公司沒有網(wǎng)站嗎做優(yōu)化的網(wǎng)站
  • 常德網(wǎng)站制作sem推廣是什么意思呢
  • ui參考網(wǎng)站網(wǎng)站優(yōu)化排名金蘋果下拉
  • mobile網(wǎng)站開發(fā)百度大搜推廣開戶
  • 如何在aws上創(chuàng)建wordpress北京seo公司助力網(wǎng)絡(luò)營銷
  • 襄陽營銷型網(wǎng)站建設(shè)網(wǎng)站排名優(yōu)化外包
  • 網(wǎng)站建設(shè)糾紛怎么投訴自己怎樣開網(wǎng)站
  • wordpress博客xiu北京seo供應(yīng)商
  • asp.net 旅游網(wǎng)站開發(fā)淘寶seo優(yōu)化排名