MyBatis-Plus常用功能教程

MyBatis-PlusMyBatis的增强工具,在其基础上只做增强不做改变,简化开发。无侵入,损耗小,内置强大的CRUD操作,支持Lambda形式调用,内置代码生成器、分页插件且支持多种数据库。

官网:https://www.baomidou.com
官方文档:https://www.baomidou.com/introduce/

MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。因此可以用MybatisPlus的starter代替Mybatis的starter。

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
  <version>Latest Version</version>
</dependency>

微服务是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。


1 快速入门

1.1 BaseMapper

以User表的增删改查为例

自定义的Mapper继承MybatisPlus提供的BaseMapper接口(继承泛型为实体类类型)。该接口已预设了各种增删改查操作的方法,可直接使用,且与原MyBatis编写的方法不冲突。

public interface UserMapper extends BaseMapper<User> {
}

在这里插入图片描述

1.2 常用注解

MP通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。根据以下方式获取实现CRUD的数据库表信息。

  • 类名驼峰转下划线作为表名
  • 名为id的字段作为主键
  • 变量名驼峰转下划线作为表的字段名

在这里插入图片描述
可通过在实体类上添加如下注解来自行指定信息:

  • @TableName:用来指定表名
  • @TableId:用来指定表中的主键字段信息,可设置主键名value及其类型type
    有如下几种IdType枚举
    1. AUTO:数据库自增长
    2. INPUT:通过set方法自行输入
    3. ASSIGN_ID:分配ID,接口ldentifierGenerator的方法nextld来生成id,默认实现类为DefaultldentifierGenerator(雪花算法)
  • @TableField:用来指定表中的普通字段信息
    一般在如下情形中使用
    • 成员变量名与数据库字段名不一致
    • 成员变录名以is开头,且为Boolean(会默认去掉is,因此需手动插入下划线)
    • 成员变量名与数据库关键字冲突(如order,可在外添加一层(`)号)
    • 成员变量不是数据库字段(需设置属性exist = false

在这里插入图片描述

1.3 常见配置

MP的配置项继承了MyBatis原生配置和一些自己特有的配置,详见官方文档。

mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po	# 别名扫描包
  mapper-locations: "classpath*:/mapper/**/*.xml"	# Mapper.xml文件地址,默认值
  configuration:
    map-underscore-to-camel-case: true	# 是否开启下划线和驼峰的映射
    cache-enabled: false	# 是否开启二级缓存
  global-config:
    db-config:
      id-type: assign_id		# id为雪花算法生成
      update-strategy: not_null	# 更新策略:只更新非空字段

1.4 一些基本概念

  • POJO(Plain Ordinary Java Object) :简单java对象。POJO是最常见最多变的对象,是一个中间对象,一个POJO持久化以后就是PO,直接用它传递、传递过程中就是DTO,直接用来对应表示层就是VO。
  • VO(View Object):视图对象,主要对应界面显示的数据对象。它的作用是把某个指定页面(或组件)的所有数据封装起来。如果是一个DTO对应一个VO,则DTO等价于VO;但是如果一个DTO对应多个VO,则展示层需要把VO转换为服务层对应方法所要求的DTO,传送给服务层,从而达到服务层与展示层解耦的效果。对于一个WEB页面,或者SWT、SWING的一个界面,用一个VO对象对应整个界面的值。
  • BO(business object):业务对象,主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。比如一个简历,有教育经历、工作经历、社会关系等等。可以把教育经历对应一个PO,工作经历对应一个PO,社会关系对应一个PO。建立一个对应简历的BO对象处理简历,每个BO包含这些PO。这样处理业务逻辑时,我们就可以针对BO去处理。BO包括包含方法的实体对象(Entity Object)和不包含方法的值对象(VO)。
  • DTO(Data Transfer Object):数据传输对象,主要用于远程调用等需要大量传输对象的地方。比如一张表有100个字段,那么对应的PO就有100个属性。但是界面上只要显示10个字段,客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端,这时就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构。到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO。这里泛指用于展示层与服务层之间的数据传输对象。
  • DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。它用来接收数据库对应的实体,是一种抽象化的数据状态,介于数据库与业务逻辑之间。一般在业务逻辑层(Service)对数据库(SQL) 进行访问时,用于接收数据。xxxDO,xxx即为数据表名。另外,DO与Entity的不同点就是DO是与数据库存在着某种映射关系的Entity,总的来说DO是Entity的一种。
  • PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。最形象的理解就是一个PO就是数据库中的一条记录,好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。

2 核心功能

2.1 条件构造器

MP的各种条件构造器(Wrapper)支持各种复杂的where条件,可以满足日常开发的所有需求。

在这里插入图片描述在这里插入图片描述

2.1.1 QueryWrapper

QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分。

【案例】基于QueryWrapper的查询

SQL语句

# 例1:查询名字中带o的,存款大于等于1000元的人的id、username、info、balance字段
select id, username, info, balance
from user
where username like 'o' and balance >= 1000

# 例2:更新用户名为jack的用户余额为2000
update user
	set balance = 2000
	where (username = 'jack')

MP操作

/* 例1:查询名字中带o的,存款大于等于1000元的人的id、username、info、balance字段 */
@Test
void testQueryWrapper1() {
	// 构建查询条件
	QueryWrapper<User> wrapper = new QueryWrapper<>()
			.select("id", "username", "info", "balance");
			.like("username", "o")
			.ge("balance", 1000)
	// 查询
	List<User> users = userMapper.selectList(wrapper);
	users.forEach(System.out::println);
}

/* 例2:更新用户名为jack的用户余额为2000 */
@Test
void testQueryWrapper2() {
	// 要更新的数据
	User user = new User();
	user.setBalance(2000);
	// 更新的条件
	QueryWrapper<User> wrapper = new QueryWrapper<>().eq("username", "jack");
	// 执行更新
	userMapper.update(user, wrapper);
}

2.1.2 UpdateWrapper

UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用。

【案例】基于UpdateWrapper的更新

SQL语句

# 例:更新id为1、2、4的用户的余额,扣减指定值200
update user
	set balance = balance - 200
	where id in (1, 2, 4)

MP操作

/* 例:更新id为1、2、4的用户的余额,扣减指定值200 */
@Test
void testUpdateWrapper() {
	List<Long> ids = List.of(1L, 2L, 4L);
	UpdateWrapper<User> wrapper = new UpdateWrapper<>()
			.setSql("balance = balance - 200")
			.in("id", ids);
	userMapper.update(null, wrapper);
}

2.1.3 Lambda条件构造器

使用LambdaQueryWrapper和LambdaUpdateWrapper可以避免硬编码。

【例】使用LambdaQueryWrapper修改2.1.1中的构建查询条件,其他无需更改

/* testQueryWrapper1 */
// 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>()
		.select(User::getId, User::getUsername, User::getInfo, User::getBalance);
		.like(User::getUsername, "o")
		.ge(User::getBalance, 1000);

2.2 自定义SQL

可以利用MP的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。

在这里插入图片描述
【案例】将id在指定范围的用户(例如1、2、4)的余额扣减指定值

MP自定义SQL的方式如下:

  1. 基于Wrapper构建where条件
/* Service层 */
/* 例:customSqlSegment示例 */
List<Long> ids = List.of(1L, 2L, 4L);
int amount = 200;
// 1. 构建条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>().in(User::getId, ids);
// 2. 自定义SQL方法调用
userMapper.updateBalanceByIds(wrapper, amount);	// updateBalanceByIds()为自定义方法
  1. 在mapper方法参数中用@Param注解声明wrapper变量名称(必须为"ew",或用Constants.WRAPPER
/* UserMapper.java */
void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);
  1. 自定义SQL,并使用Wrapper条件
<update id="updateBalanceByIds">
	update tb_user set balance = balance - #{amount} ${ew.customSqlSegment}
</update>

2.3 IService接口

MP提供了Service层的IService接口与ServiceImpl实现类。

2.3.1 基本用法

之前的自定义接口继承IService接口,自定义实现类在实现自定义接口的同时继承ServiceImpl实现类。

在这里插入图片描述

在这里插入图片描述

/* UserService.java */
public interface IUserService extends IService<User> {
}
/* UserServiceImpl.java */
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

2.3.2 CRUD案例

【案例】基于Restful风格实现下列接口

在这里插入图片描述

导入swagger(后端API接口文档注解)、web依赖

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

配置swagger信息

# application.yaml
knife4j:
  enable: true
  openapi:
    title: 用户管理接口文档
    description: "用户管理接口文档"
    email: akira37@foxmail.com
    concat: Akira
    url: https://www.hyperplasma.cn
    version: v1.0.0
    group:
      default:
        gqroup-name: default
        api-rule: package
        api-rule-resources:
          - com.hyplus.mp.controller

事先实现用户POUser、用户表单实体UserFormDTO、用户VO实体UserVO

设置Controller时推荐用构造函数注入userService,使用Lombok自动生成。可用BeanUtil工具包实现PO、DTO、VO等之间的转换。

/* UserController.java */
@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsContructor		// 只生成必备参数(如final)的有参构造
public class UserController {
	private final IUserService userService;

	@ApiOperation("新增用户接口")
	@PostMapping
	public void saveUser(@RequestBody UserFormDTO userDTO) {
		// 简单操作可直接在Controller中完成
		// 1. 把DTO拷贝到PO(使用BeanUtil包中的copyProperties实现类型转换)
		User user = BeanUtil.copyProperties(userDTO, User.class)
		// 2. 新增
		userService.save(user);
	}

	@ApiOperation("删除用户接口")
	@DeleteMapping("{id}")
	public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
		userService.removeById(id);
	}

	@ApiOperation("根据id查询用户接口")
	@GetMapping("{id}")
	public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
		// 1. 查询用户PO
		User user = userService.getById(id);
		// 2. 把PO拷贝到VO
		return BeanUtil.copyProperties(user, UserVO.class);
	}

	@ApiOperation("根据id批量查询用户接口")
	@GetMapping
	public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
		// 1. 查询用户PO
		List<User> users =  userService.listByIds(ids);
		// 2. 把PO拷贝到VO
		return BeanUtil.copyToList(users, UserVO.class);
	}

	@ApiOperation("扣减用户余额")
	@PutMapping("/{id}/deduction/{money}")
	public void deductMonyById(@ApiParam("用户id") @PathVariable("id") Long id,
							   @ApiParam("扣减的金额") @PathVariable("money") Integer money) {
		// 复杂业务则留给Service层
		userService.deductBalance(id, money);
	}
}
/* UserService.java */
public interface IUserService extends IService<User> {
	void deductBalance(Long id, Integer money);
}
/* UserServiceImpl.java */
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
	// ...

	@Override
	public void deductBalance(Long id, Integer money) {
		// 1. 查询用户(法1: 直接调用预置方法,法2: 使用继承所得的baseMapper)
		User user = getById(id);
		// 2. 检验用户
		if (user == null || user.getStatus == 2) {
			throw new RuntimeException("用户状态异常!");
		}
		// 3. 校验余额是否充足
		if (user.getBalance() < money) {
			throw new RuntimeException("用户余额不足!");
		}
		// 4. 扣减余额 update tb_user set balance = balance - ?
		baseMapper.deductBalance(id, money);
	}
}
/* UserMapper.java */
public interface UserMapper extends BaseMapper<User> {
	@Update("update tb_user set balance = balance - #{money} where id = #{id}")
	void deductBalance(@Param("id") Long id, @Paran("money") Integer money);
}

2.3.3 Lambda查询与更新

使用lambdaQuery()ambdaUpdate()可直接链式编程构建条件,且简化部分条件判断的写法(如判非空)。

【案例1】实现一个根据复杂条件查询用户的接口,查询条件如下:
— name:用户名关键字,可以为空
— staitus:用户状态,可以为空
— minBalance:最小余额,可以为空
— maxBalance:最大余额,可以为空

对于极复杂的条件查询,可另定义UserQuery类来接收查询条件。

/* UserController.java */
@ApiOperation("根据复杂条件查询用户接口")
@GetMapping("/list")
public List<UserVO> queryUsers(UserQuery query) {
	List<User> users =  userService.queryUsers(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
	return BeanUtil.copyToList(users, UserVO.class);
}
/* UserService.java */
List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);
/* UserServiceImpl.java */
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
	// 使用lambdaQuery
	return lambdaQuery()
			.like(name != null, User::getUsername(), name)	// 先判断条件name != null,下同
			.eq(status != null, User::getStatus(), status)
			.ge(minBalance != null, User::getBalance(), minBalance)
			.le(maxBalance != null, User::getBalance(), maxBalance)
			.list();	// 返回列表
}

【案例2】改造2.3.2中根据id修改用户余额的接口,要求追加:如果扣减后余额为0,则将用户status修改为冻结状态(2

/* UserServiceImpl.java */
@Override
public void deductBalance(Long id, Integer money) {
	User user = getById(id);
	if (user == null || user.getStatus == 2) {
		throw new RuntimeException("用户状态异常!");
	}
	if (user.getBalance() < money) {
		throw new RuntimeException("用户余额不足!");
	}
	// 使用lambdaUpdate
	int remainBalance = user.getBalance() - money;
	lambdaUpdate()
			.set(User::getBalance, remainBalance)
			.set(remainBalance == 0, User::getStatus, 2)
			.eq(User::getId, id);
			.eq(User::getBalance, user.getBalance())	// 添加乐观锁(先比较再更新)
			.update();		// 执行更新
}

2.3.4 批处理方案

IService的批量插入显著快于for循环,因为MP采用预编译方案,减少网络请求。

【测试】批量插入10万条用户数据,并对比普通for循环插入与IService的批量插入

@Test
void testSaveBatch() {
	// 每次批量插入1000条,插入100次即10万条数据
	// 1. 准备容量为1000的集合
	List<User> list = new ArrayList<>(1000);
	long b = System.currentTimeMillis();
	for (int i = 1 i <= 100000; i++) {
		// 2. 添加一个user
		list.add(buildUser(i));
		// 3. 每1000条批量插入一次
		if (i % 1000 == 0) {
			userService.saveBatch(list);
			// 4. 清空集合,准备下一批数据
			list.clear();
		}
	}
	long e = System.currentTimeMillis();
	System.out.println("耗时:" + (e - b));
}
  • 普通for循环逐条插入速度极差,不推荐
  • MP的批量新增,基于预编译的批处理,性能不错
  • 进一步优化:把多条SQL语句优化成一条。
    配置MySQL的jdbc参数,追加开启&rewriteBatchedStatements=true,性能最好

3 扩展功能

3.1 代码生成器

新版代码生成器:https://www.baomidou.com/guides/new-code-generator/

建议配合Mybatis X插件使用,或者用IDEA中的MyBatisPlus同名插件:

在这里插入图片描述

先点Config Database配置数据库信息,再点Code Generator生成代码

在这里插入图片描述在这里插入图片描述

3.2 静态工具

新版MP中的Db静态工具提供了一系列与sevice功能类似的静态方法,需要告知实体类类型

可使用静态工具解决循环依赖的情况。

在这里插入图片描述

【案例】静态工具查询,需求:
— 改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址(使用CollUtil包判断地址集合是否为空)
— 改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址
— 实现根据用户id查询收货地址功能(已新建对应的AddressVO,并给UserVO追加用户收货地址addresses属性),需要验证用户状态,冻结用户抛出异常(练习)

/* UserController.java */
@ApiOperation("根据id查询用户接口")
@GetMapping("{id}")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
	return userService.queryUserAndAdressById(id);
}

@ApiOperation("根据id批量查询用户接口")
@GetMapping
public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
	returns userService.queryUserAndAdressByIds(ids);
}

/* UserService.java */
UserVO queryUserAndAdressById(Long id);

List<UserVO> queryUserAndAddressByIds(List<Long> ids);
/* UserServiceImpl.java */
@OVerride
public UserVO queryUserAndAdressById(Long id) {
	// 1. 查询用户
	User user = getById(id);
	if (user == null || user.getStatus() == 2) {
		throw new RuntimeException("用户状态异常!");
	}
	
	// 2. 查询地址(注意Db的选择,使用Lambda查询,传入Address的字节码,储存为List)
	List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, id).list();
	
	// 3. 封装VO
	// 3.1. 转User的PO为VO
	BeanUtil.copyProperties(user, UserVO.class);
	// 3.2. 转地址VO(使用CollUtil包判断地址集合是否为空)
	if (CollUtil.isNotEmpty(addresses)) {
		userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
	}
	return userVO;
}

@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
	// 1. 查询用户
	List<User> users = listByIds(ids);
	if (CollUtil.isEmpty(users)) {
		return Collections.emptyList();
	}
	
	// 2. 查询地址
	// 2.1. 获取用户id集合
	List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
	// 2.2. 根据用户id查询地址
	List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();
	// 2.3. 转换地址VO
	List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
	// 2.4. 用户地址集合分组整理,相同用户的放入一个集合中
	Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
	if (CollUtil.isNotEmpty(addressVOList)) {
		addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
	}

	// 3. 转换VO返回
	List<UserVO> list = new ArrayList<>(users.size());
	for (User user : users) {
		// 3.1. 转换User的PO为VO
		UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
		list.add(vo);
		// 3.2. 转换地址VO
		vo.setAddresses(addressMap.get(user.getId()));
	}
	return list;
}

3.3 逻辑删除

逻辑删除:基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下:

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为1
  • 查询时只查询标记为0的数据

【例】逻辑删除字段为deleted:

# 删除操作
update tb_user set deleted = 1 where id = 1 and deleted = 0

# 查询操作
select * from tb_user where deleted = 0

MP提供了逻辑删除功能,无需改变方法调用的方式,而是在底层自动修改CRUD的语句。在application.yaml文件中配置逻辑删除的字段名称和值即可:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag	# 全局逻辑删除的实体字段名,字段类型可以是boolean、integer
      logic-delete-value: 1		# 逻辑已删除值(默认为1)
      logic-not-delete-value: 0	# 逻辑未删除值(默认为0)

逻辑删除本身也有自己的问题:

  • 会导致数据库表垃圾数据越来越多,影响查询效率
  • SQL中全都需要对逻辑删除字段做判断,影响查询效率

因此不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

3.4 枚举处理器

MP对MyBatis的各种类型处理器进行了扩展,其中包括新的MybatisEnumTypeHandler枚举处理器,有效解决枚举的类型转换问题。

步骤:

  1. 在枚举中给枚举值(对应于数据库字段)添加注解@EnumValue
    枚举默认做JSON处理时以枚举名(如下例中的NORMAL)返回,给枚举注释添加@JsonValue来告诉SpringMVC返回它。
/* UserStatus.java */
@Getter
public enum UserStatus {
	NORMAL(1, "正常"),		// 枚举注释
	FREEZE(2, "冻结")
	;
	
	@EnumValue
	private final int value;
	@JsonValue
	private final String desc;
	
	UserStatus(int value, String desc) {
		this.value = value;
		this.desc = desc;
	}
}
/* User.java */
// ...
private UserStatus status;	// 使用状态(使用枚举作为属性)
  1. 在application.yml中配置全局枚举处理器
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

3.5 JSON处理器

MP提供了各种JSON处理器来解决JSON和Java数据类型之间的转换。建议使用JacksonTypeHandler(因为这也是SpringMVC默认),通过添加注解@TableName@TableField来实现。

对象嵌套会产生复杂resultMap,打开autoResultMap,将查询结果的不同部分映射到嵌套对象及其属性上。

/* User.java */
@Data
@TableName(value = "user", autoResultMap = true)
public class User {
	private Long id;

	private String username;
	
	@TableField(typeHandler = JacksonTypeHandler.class)
	private UserInfo info;
	
	// ...
}
/* UserInfo.java 用于接收表中JSON类型数据 */
@Data
public class UserInfo {
	private Integer age;
	private String intro;
	private String gender;
}

赋值时改为使用构建器

@Test
void testInsert() {
	User user = new User();
	user.setUsername("Teresa");
	user.setInfo(UserInfo.of(17, "Sheeps 3", "female"));		// UserInfo构建器
	// ...
	userMapper.insert(user);
}

4 插件功能

MP的插件基于拦截器,拦截SQL的执行并拓展功能。
其中最常用的是分页插件,从此无需再用PageHelper。

在这里插入图片描述

4.1 分页插件

详见官方文档

首先要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件:初始化时需指定数据库类型,调用addInnerInterceptor()添加插件。可设置分页上限,避免一次查询过多。

@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 1.初始化核心插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 2.添加分页插件
        PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        pageInterceptor.setMaxLimit(1000L);	// 设置分页上限
        interceptor.addInnerInterceptor(pageInterceptor);
        return interceptor;
    }
}

之后即可享用IService接口内置的分页API,源码如下。其中泛型E是IPage的泛型及其子类(Page,PageDTO),常用Page,包含页码、每页展示数、排序方式等,调用时传入Page对象、Wrapper查询条件,查询出参数填入page返回。

在这里插入图片描述

示例:

@Test
void testPageQuery() {
	// 1. 查询
	int pageNo = 1, pageSize = 5;
	// 1.1 分页参数
	Page<User> page = Page.of(pageNo, pageSize);
	// 1.2. 排序参数,通过OrderItem来指定:true - 升序,false - 降序
	page.addOrder(new OrderItem("balance", false));
	// 1.3. 分页查询
	Page<User> p = userService.page(page);

	// 2. 总条数
	System.out.println("total = " + p.getTotal());
	// 3. 总页数
	System.out.println("pages = " + p.getPages());
	// 4. 分页数据
	List<User> records = p.getRecords();
	records.forEach(System.out::println);
}

4.2 通用分页实体

当分页查询参数过多时,需要创建分页查询实体

【案例1】遵循下面的接口规范,编写一个UserController接口,实现User的分页查询

在这里插入图片描述

单独定义通用分页实体PageQuery,并让UserQuery继承它:

/* PageQuery.java 通用分页实体 */
@Data
@ApiModel("通用分页实体")
public class PageQuery {
	@ApiModelProperty("页码")
	private Integer pageNo;
	
	@ApiModelProperty("页数")
	private Integer pageSize;

	@ApiModelProperty("排序字段")
	private String sortBy;

	@ApiModelProperty("是否升序")
	private Boolean isAsc;
}
/* UserQuery.java */
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
	@ApiModelProperty("用户名关键字")
	private String name;

	@ApiModelProperty("用户状态:1-正常,2-冻结")
	private Integer status;

	@ApiModelProperty("余额最小值")
	private Integer minBalance;

	@ApiModelProperty("余额最大值")
	private Integer maxBalance;
}

定义分页结果PageDTO(使用泛型):

/* PageDTO.java */
@Data
@ApiModel(description = "分页结果")
public class PagDTO<T> {
	@ApiModelProperty("总条数")
	private Long total;
	
	@ApiModelProperty("总页数")
	private Long pages;
	
	@ApiModelProperty("集合")
	private List<T> list;
}

分页查询接口:

/* UserController.java */
@ApiOperation("根据条件分页查询用户接口")
@GetMapping("/page")
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
	return userService.queryUsersPage(query);
}
/* UserService.java */
PageDTO<UserVO> queryUsersPage(UserQuery query);
/* UserServiceImpl.java */
@Override
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
	String name = query.getName();
	Integer status = query.getStatus();
	// 1. 构建查询条件
	// 1.1. 分页条件
	Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
	// 1.2. 排序条件(使用StrUtil工具包中的判非空)
	if (StrUtil.isNotBlank(query.getSortBy())) {
		page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
	} else {
		page.addOrder(new OrderItem("update_time", false));
	}
	
	// 2. 分页查询
	Page<User> p = lambdaQuery()
			.like(name != null, User::getUsername(), name)
			.eq(status != null, User::getStatus(), status)
			.page(page);
			
	// 3. 封装VO结果
	PageDTO<UserVO> dto = new PageDTO<>();
	// 3.1. 总条数
	dto.setTotal(p.getTotal());
	// 3.2. 总页数
	dto.setPages(p.getPages());
	// 3.3. 当前页数据
	List<User> records = p.getRecords()
	if (CollUtil.isEmpty(records)) {
		dto.setList(Collections.emptyList());
	} else {
		// 3.4. 拷贝user的VO
		dto.setList(BeanUtil.copyToList(records, UserVO.class));
	}
	
	// 4. 返回
	return dto;
}

4.3 分页方法

【案例2】4.2中封装分页条件和解析过程繁琐,逻辑通用,封装为工具
— 在PageQuery中定义方法,将PageQuery对象转为MyBatisPlus中的Page对象
— 在PageDTO中定义方法,将MyBatisPlus中的Page结果转为PageDTO结果

/* PageQuery.java */
@Data
@ApiModel("通用分页实体")
public class PageQuery {
	@ApiModelProperty("页码")
	private Integer pageNo = 1;		// 添加默认值
	
	@ApiModelProperty("页数")
	private Integer pageSize = 5;

	@ApiModelProperty("排序字段")
	private String sortBy;

	@ApiModelProperty("是否升序")
	private Boolean isAsc = true;
	
	public <T> Page<T> toMpPage(OrderItem ...items) {
		// 1. 分页条件
		Page<T> page = Page.of(pageNo, PageSize);
		// 2. 排序条件
		if (StrUtil.isNotBlank(sortBy)) {
			page.addOrder(new OrderItem(sortBy, isAsc));
		} else if (items != null) {
			page.addOrder(items);	// 默认排序方式为可变参数
		}
		return page;
	}
	
	public <T> Page<T> toMpPage(String defaultSortBy, Boolean defaultAsc) {
		return toMpPage(new OrderItem(defaultSortBy, defaultAsc));
	}

	public <T> Page<T> toMpPageDefaultSortByCreateTime() {
		return toMpPage(new OrderItem("create_time", false));
	}

	public <T> Page<T> toMpPageDefaultSortByUpdateTime() {
		return toMpPage(new OrderItem("update_time", false));
	}
}
/* UserQuery.java */
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
	@ApiModelProperty("用户名关键字")
	private String name;

	@ApiModelProperty("用户状态:1-正常,2-冻结")
	private Integer status;

	@ApiModelProperty("余额最小值")
	private Integer minBalance;

	@ApiModelProperty("余额最大值")
	private Integer maxBalance;

	public static <PO, VO> PageDTO<VO> of(Page<PO> p, Function<PO, VO> convertor) {
		PageDTO<VO> dto = new PageDTO<>();
		// 1. 总条数
		dto.setTotal(p.getTotal());
		// 2. 总页数
		dto.setPages(p.getPages());
		// 3. 当前页数据
		List<PO> records = p.getRecords()
		if (CollUtil.isEmpty(records)) {
			dto.setList(Collections.emptyList());
		} else {
			// 转换操作
			dto.setList(records.stream().map(convertor).collect(Collectors.toList()));
		}
		return dto;
	}
}

最终改进后的分页查询方法:

/* UserServiceImpl.java */
@Override
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
	String name = query.getName();
	Integer status = query.getStatus();
	// 1. 构建查询条件
	Page<User> p = query.toMpPageDefaultSortByUpdateTime()
	
	// 2. 分页查询
	Page<User> p = lambdaQuery()
			.like(name != null, User::getUsername(), name)
			.eq(status != null, User::getStatus(), status)
			.page(page);
			
	// 3. 封装VO结果
	return PageDTO.of(p, user -> {
		// 1) 拷贝基础属性
		UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
		// 2) 处理特殊逻辑(如省略用户名后两位等)
		vo.setUsername(vo.getUsername().substring(0, vo.getUsername().length() - 2) + "**");
		return vo;
	});
}
  • 31
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Akira37

💰unneeded

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值