如果本文有不清楚的解释或步骤可以直接点击下方链接跳转查看哦~ 图片是在某人图库里面发现的,或许他喜欢风铃公主这样的?

快速开始Mybatis-Plus

首先是最最最重要的官方文档:https://baomidou.com/

  • 步骤一:引入Mybatis-Plus依赖,代替Mybatis依赖
1
2
3
4
5
6
7
8
9
10
<!--<dependency>-->
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- <version>2.3.1</version>-->
<!--</dependency>-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
  • 步骤二:定义mapper接口并继承BaseMapper
1
2
3
public interface UserMapper extends BaseMapper<User> {

}

本文用到的数据库如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
-- 导出 mp 的数据库结构
CREATE DATABASE IF NOT EXISTS `mp` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `mp`;

-- 导出 表 mp.address 结构
CREATE TABLE IF NOT EXISTS `address` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL COMMENT '用户ID',
`province` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '省',
`city` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '市',
`town` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '县/区',
`mobile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '手机',
`street` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '详细地址',
`contact` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '联系人',
`is_default` bit(1) DEFAULT b'0' COMMENT '是否是默认 1默认 0否',
`notes` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
`deleted` bit(1) DEFAULT b'0' COMMENT '逻辑删除',
PRIMARY KEY (`id`) USING BTREE,
KEY `user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=71 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT;

-- 正在导出表 mp.address 的数据:~11 rows (大约)

INSERT INTO `address` (`id`, `user_id`, `province`, `city`, `town`, `mobile`, `street`, `contact`, `is_default`, `notes`, `deleted`) VALUES
(59, 2, '北京', '北京', '朝阳区', '13900112222', '金燕龙办公楼', 'Rose', b'1', NULL, b'0'),
(60, 1, '北京', '北京', '朝阳区', '13700221122', '修正大厦', 'Jack', b'0', NULL, b'0'),
(61, 1, '上海', '上海', '浦东新区', '13301212233', '航头镇航头路', 'Jack', b'1', NULL, b'0'),
(63, 2, '广东', '佛山', '永春', '13301212233', '永春武馆', 'Rose', b'0', NULL, b'0'),
(64, 3, '浙江', '杭州', '拱墅区', '13567809102', '浙江大学', 'Hope', b'1', NULL, b'0'),
(65, 3, '浙江', '杭州', '拱墅区', '13967589201', '左岸花园', 'Hope', b'0', NULL, b'0'),
(66, 4, '湖北', '武汉', '汉口', '13967519202', '天天花园', 'Thomas', b'1', NULL, b'0'),
(67, 3, '浙江', '杭州', '拱墅区', '13967589201', '左岸花园', 'Hopey', b'0', NULL, b'0'),
(68, 4, '湖北', '武汉', '汉口', '13967519202', '天天花园', 'Thomas', b'1', NULL, b'0'),
(69, 3, '浙江', '杭州', '拱墅区', '13967589201', '左岸花园', 'Hopey', b'0', NULL, b'0'),
(70, 4, '湖北', '武汉', '汉口', '13967519202', '天天花园', 'Thomas', b'1', NULL, b'0');

-- 导出 表 mp.user 结构
CREATE TABLE `user` (
`id` BIGINT(19) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` VARCHAR(50) NOT NULL COMMENT '用户名' COLLATE 'utf8_general_ci',
`password` VARCHAR(128) NOT NULL COMMENT '密码' COLLATE 'utf8_general_ci',
`phone` VARCHAR(20) NULL DEFAULT NULL COMMENT '注册手机号' COLLATE 'utf8_general_ci',
`info` JSON NOT NULL COMMENT '详细信息',
`status` INT(10) NULL DEFAULT '1' COMMENT '使用状态(1正常 2冻结)',
`balance` INT(10) NULL DEFAULT NULL COMMENT '账户余额',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username` (`username`) USING BTREE
)
COMMENT='用户表'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
ROW_FORMAT=COMPACT
AUTO_INCREMENT=5
;

-- 正在导出表 mp.user 的数据:~4 rows (大约)

INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `status`, `balance`, `create_time`, `update_time`) VALUES
(1, 'Jack', '123', '13900112224', '{"age": 20, "intro": "佛系青年", "gender": "male"}', 1, 1600, '2023-05-19 20:50:21', '2023-06-19 20:50:21'),
(2, 'Rose', '123', '13900112223', '{"age": 19, "intro": "青涩少女", "gender": "female"}', 1, 600, '2023-05-19 21:00:23', '2023-06-19 21:00:23'),
(3, 'Hope', '123', '13900112222', '{"age": 25, "intro": "上进青年", "gender": "male"}', 1, 100000, '2023-06-19 22:37:44', '2023-06-19 22:37:44'),
(4, 'Thomas', '123', '17701265258', '{"age": 29, "intro": "伏地魔", "gender": "male"}', 1, 800, '2023-06-19 23:44:45', '2023-06-19 23:44:45');

常用注解

MybatisPlus中比较常用的几个注解如下:

  • @TableName:用来指定表名

  • @TableId:用来指定表中的主键字段信息

    • IdType枚举:
      • AUTO:数据库自增长
      • INPUT:通过set方法自行输入
      • ASSIGN_ID:分配 ID,接口IdentifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法
  • @TableField:用来指定表中的普通字段信息。常见使用类型:

    • 成员变量名与数据库字段名不一致
    • 成员变量名以is开头,且是布尔值
    • 成员变量名与数据库关键字冲突
    • 成员变量不是数据库字段
1
2
3
4
5
6
7
8
9
10
11
12
13
@TableName("tb_user")
public class User{
@TableId(value="id", type= IdType.Auto)
private Long id;
@TableField("username")
private String name;
@TableField("is_marries")
private Boolean isMarries;
@TableField("`order`")
private Integer order;
@TableField(exist = false)
private String address;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 使用@TableName注解指定这个类映射的数据库表名为"tb_user"
@TableName("tb_user")
public class User {
// 使用@TableId注解指定这个字段为表的主键,value属性指定数据库表中的列名为"id",
// type=IdType.Auto表示主键的生成策略为自动增长(适用于支持自动增长主键的数据库)
@TableId(value="id", type= IdType.Auto)
private Long id; // 用户ID

// 使用@TableField注解指定这个字段映射到数据库表中的列名为"username"
@TableField("username")
private String name; // 用户名,这里字段名与数据库列名不完全一致(数据库列为username,类字段为name),
// 因此需要通过@TableField明确映射关系

// 使用@TableField注解指定这个字段映射到数据库表中的列名为"is_marries"
@TableField("is_marries")
private Boolean isMarries; // 用户是否已婚

// 使用@TableField注解指定这个字段映射到数据库表中的列名为"order"(由于order是SQL的关键字,
// 因此使用反引号`将其包围,以避免解析错误)
@TableField("`order`")
private Integer order; // 排序字段或某种顺序标识

// 使用@TableField注解的exist=false属性表示这个字段不是数据库表中的列,
// 即这个字段不会被MyBatis-Plus的CRUD操作所考虑(不会被插入到数据库或从数据库查询出来)。
// 这通常用于一些只在业务逻辑中使用,而不需要持久化到数据库的字段。
@TableField(exist = false)
private String address; // 用户地址,这个字段不会被映射到数据库表中
}

MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。例如:

1
2
3
4
5
6
7
8
9
10
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: assian.id # id为雪花算法生成
update-strategy: not_null #更新策略:只更新非空字段

总结MyBatisPlus使用的基本流程

① 引入起步依赖
② 自定义Mapper基础BaseMapper
③ 在实体类上添加注解声明 表信息
④ 在application.yml中根据需要添加配置

ദ്ദി˶>ᴗo)✧MybatisPlus是如何获取实现CRUD的数据库表信息的?

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

核心功能

wrapper

Wrapper是MyBatis-Plus提供的一种查询条件封装类,用于构建查询条件或更新条件。它是一个抽象类,拥有多个具体的实现类,如QueryWrapper、UpdateWrapper、LambdaQueryWrapper等。这些实现类提供了丰富的方法来构建各种查询或更新条件。
wrapper的常用方法:

eq(String column, Object value):等于查询,相当于SQL中的“=”
ne(String column, Object value):不等于查询,相当于SQL中的“<>”
gt(String column, Object value):大于查询,相当于SQL中的“>”
ge(String column, Object value):大于等于查询,相当于SQL中的“>=”
lt(String column, Object value):小于查询,相当于SQL中的“<”
le(String column, Object value):小于等于查询,相当于SQL中的“<=”
like(String column, Object value):模糊查询,相当于SQL中的“LIKE”
in(String column, Collection<?> values):IN查询,相当于SQL中的“IN”
isNull(String column):为空查询,相当于SQL中的“IS NULL”
isNotNull(String column):不为空查询,相当于SQL中的“IS NOT NULL”
orderByAsc(String… columns):升序排序,相当于SQL中的“ORDER BY … ASC”
orderByDesc(String… columns):降序排序,相当于SQL中的“ORDER BY … DESC”

基于QueryWrapper的查询

需求:
①查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance字段
②更新用户名为jack的用户的余额为2000

  • 手写sql文
1
2
3
SELECT id,username,info,balabce
FROM user
WHERE username LIKE ? AND balance >= ?
1
2
3
UPDATE user
SET balance = 2000
WHERE (username = "jack")
  • 使用 QueryWrapper 来构建查询条件并执行查询
1
2
3
4
5
6
7
8
9
10
11
void testQueryWrapper() {
//1、构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info","balance")
.like("username","o")
.ge("balance", 1000);
//2、查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void testQueryWrapper() {
//1、构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info","balance")
.like("username","o")
.ge("balance", 1000);
//2、查询
List<User> users = userMapper.selectList(wrapper);
// 3、处理查询结果
// 使用forEach方法和方法引用遍历查询结果列表
// 并打印每个User对象的信息(假设User类已正确重写toString方法)
users.forEach(System.out::println);
// 注意:这里的System.out::println是Java 8引入的方法引用语法
// 它相当于对每个user对象调用System.out.println(user)
}
// 1. QueryWrapper<T>:是MyBatis-Plus提供的查询构造器,用于构建SQL查询条件。 T代表实体类类型,在这个例子中是User类。
// 2. select(...):指定查询结果中需要包含的列。
// 3. like(...):添加模糊查询条件。
// 4. ge(...):添加大于等于的条件,ge是greater than or equal的缩写。
// 5. userMapper:是MyBatis-Plus自动生成的Mapper接口,用于操作数据库。
// 6. selectList(...):是Mapper接口中的一个方法,用于执行查询并返回结果列表。
// 7. List<T>:是Java中的泛型集合,用于存储T类型的对象。
// 8. forEach(...):是Java 8引入的Stream API中的一个方法,用于遍历集合。
// 9. System.out::println:是Java 8引入的方法引用语法,用于引用静态方法。
  • 使用QueryWrapper 作为条件更新User表中数据的方法
1
2
3
4
5
6
7
8
9
10
void testUpdateByQeryWrapper() {
//1、要更新的数据
User user = new User();
user.setBalance(2000);
//2、更新条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.eq("username", "Jack");
//3、执行更新
userMapper.update(user, wrapper);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void testUpdateByQueryWrapper() {
// 1、要更新的数据
// 创建一个User对象,用于设置需要更新的字段值
// 在这个例子中,我们想要将某个用户的balance字段更新为2000
User user = new User();
user.setBalance(2000); // 设置balance字段的值为2000

// 注意:这里只设置了需要更新的字段,其他字段将不会被更新。
// 如果User对象中有其他非空字段,并且这些字段在数据库中有对应的非空约束,
// 那么在更新时可能会遇到问题,因为MyBatis-Plus默认不会将这些字段设置为NULL,
// 除非在实体类中使用@TableField注解并设置el="null"或在全局配置中设置相关策略。

// 2、更新条件
// 创建一个QueryWrapper实例,用于构建更新操作的条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.eq("username", "Jack"); // 添加一个等于条件,表示只更新username为'Jack'的记录

// 3、执行更新
// 调用userMapper的update方法执行更新操作
// 该方法接收两个参数:一个是包含更新字段值的User对象,另一个是QueryWrapper对象作为更新条件
userMapper.update(user, wrapper);
}

// 相关说明:
// 1. User:是一个实体类,对应数据库中的User表。
// 2. QueryWrapper<T>:是MyBatis-Plus提供的查询构造器,用于构建SQL查询或更新条件。T代表实体类类型,在这个例子中是User类。
// 3. eq(...):是QueryWrapper中的一个方法,用于添加等于条件。
// 4. userMapper:是MyBatis-Plus自动生成的Mapper接口的一个实例,用于操作数据库。
// 5. update(...):是Mapper接口中的一个方法,用于根据提供的条件和更新字段值执行更新操作

基于UpdateWrapper的更新

需求:更新id为1,2,4的用户的余额,扣200

  • 手写sql
1
2
3
UPDATE user
SET balance = balance - 200
WHERE id in (1,2,4)
  • 使用UpdateWrapper来构造一个更新条件
1
2
3
4
5
6
7
void testUpdateWrapper(){
List<Long> ids = List.of(1L, 2L, 3L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance -200")
.in("id", ids);
userMapper.update(null, wrapper);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void testUpdateWrapper() {
// 创建一个包含要更新记录ID的列表
List<Long> ids = List.of(1L, 2L, 3L); // 这意味着我们将更新ID为1, 2, 3的用户记录

// 创建一个UpdateWrapper对象,用于构造更新条件
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
// 使用setSql方法指定要执行的SQL片段,这里是将用户的balance字段减去200
.setSql("balance = balance - 200")
// 使用in方法指定ID字段的值在提供的ids列表中的记录将被更新
.in("id", ids);

// 调用userMapper的update方法执行更新操作
// 注意:第一个参数通常是一个实体对象,用于指定要更新的字段和值(通过setter方法)。
// 但在这个例子中,我们传递了null,因为我们已经在setSql中指定了更新的SQL片段。
// 在实际使用中,如果不需要通过实体对象指定更新字段,可以传递null。
userMapper.update(null, wrapper);
}

基于LambdaQueryWrapper的查询

1
2
3
4
5
6
7
8
9
10
void testLambdaQueryWrapper() {
//1、构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.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);
}

使用Lambda表达式来引用实体类的属性,这些属性会被映射到数据库表的字段上,开发者不需要手动填写字段名称,而是直接引用实体类的属性名。
Lambda表达式提供了编译时的类型检查,减少了拼写错误的可能性。(感觉其实就是避免手残党手误)

总结条件构造器的用法:

  • QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
  • UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
  • 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码

自定义SQL

利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分
① 基于Wrapper构建where条件
② 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
③ 自定义SQL,并使用Wrapper条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 测试自定义SQL更新方法,用于更新用户的余额。
*/
void testCustomSqlUpdate() {
// 1、定义更新条件:指定需要更新余额的用户ID列表
// 这里我们假设要更新ID为1, 2, 4的用户的余额
List<Long> ids = List.of(1L, 2L, 4L);

// 2、定义要更新的余额数值,这里设置为200
int amount = 200;

// 3、构建查询条件,使用QueryWrapper来指定更新条件
// 这里我们通过.in("id", ids)来指定需要更新的用户ID在ids列表中
QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);

// 4、调用自定义的Mapper方法来进行更新操作
// 该方法的作用是根据给定的条件(wrapper)和余额数值(amount)来更新用户的余额。
userMapper.updateBalanceByIds(wrapper, amount);
}
1
void updateBalanceByIds(@Param("ew") QueryWrapper<User> wrapper, @Param("amount")int amount);
1
2
3
4
<update id="updateBalanceByIds">
UPDATE user
SET balance = balance + #{amount} ${ew.customSqlSegment}
</update>

Service接口

Service接口使用流程

  • 自定义 Service 接口继承 IService 接口
  • 自定义 Service 实现类,实现自定义接口并继承 ServiceImpl 类
1
2
public interface IUserService extends IService<User> {
}
1
2
3
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@SpringBootTest
public class IUserServiceTest {

// 使用@Autowired注解自动装配IUserService的bean,这样可以在测试方法中使用userService对象。
@Autowired
private IUserService userService;

// 定义一个测试方法,用于测试保存用户信息的功能。
@Test
void testSaveUser() {
// 创建一个新的User对象。
User user = new User();
// 设置用户的用户名。
user.setUsername("ln");
// 设置用户的密码。
user.setPassword("123456");
// 设置用户的手机号码。
user.setPhone("13800000000");
// 设置用户的余额。
user.setBalance(200);
// 设置用户的个人信息,这里使用JSON字符串存储年龄、介绍和性别。
user.setInfo("{\"age\": 22, \"intro\": \"英文老师\", \"gender\": \"female\"}");
// 设置用户的创建时间。
user.setCreateTime(LocalDateTime.now());
// 设置用户的更新时间。
user.setUpdateTime(LocalDateTime.now());
// 调用userService的save方法保存用户信息。
userService.save(user);
}

// 定义一个测试方法,用于测试根据ID列表查询用户信息的功能。
@Test
void testQuery(){
// 定义一个包含用户ID的列表,这里假设要查询ID为1, 2, 4的用户。
List<User> users = userService.listByIds(List.of(1L,2L,4L));
// 遍历查询结果,并打印每个用户的信息。
users.forEach(System.out::println);
}
}

为了便于进行下面的测试例子,我们来配置一下swagger
步骤一引入依赖
步骤二根据自己的项目配置application.yaml
步骤三成功啦(访问地址是http://localhost:8080/doc.html 写自己用的端口号哦)

1
2
3
4
5
6
7
8
9
10
11
<!--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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
knife4j:
enable: true
openapi:
title: 用户管理接口文档
description: "用户管理接口文档"
email: zhanghuyi@itcast.cn
concat: ln
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.itheima.mp.controller

使用上面的service接口,我们可以直接调用MP提供的方法进行一些简单的CRUD的操作,无需再自己手写service方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 用户管理接口控制器类
@RestController
@RequestMapping("/user")
@Api(tags = "用户管理接口") // Swagger标签,用于归类API
@RequiredArgsConstructor // Lombok注解,自动注入需要的依赖
public class UserController {

// 自动注入用户服务
private final IUserService userService;

/**
* 新增用户接口
*
* @param userDto 包含用户信息的DTO对象
*/
@PostMapping
@ApiOperation("新增用户")
public void saveUser(@RequestBody UserFormDTO userDto) {
// 将DTO对象转换为User实体对象
User user = BeanUtil.copyProperties(userDto, User.class);
// 调用服务层方法保存用户
userService.save(user);
}

/**
* 根据ID删除用户接口
*
* @param id 要删除的用户ID
*/
@DeleteMapping("{id}")
@ApiOperation("根据ID删除用户")
public void deleteById(@ApiParam(value = "用户ID", required = true) @PathVariable("id") Long id) {
// 调用服务层方法根据ID删除用户
userService.removeById(id);
}

/**
* 根据ID查询用户接口
*
* @param id 要查询的用户ID
* @return 包含用户信息的VO对象
*/
@GetMapping("{id}")
@ApiOperation("根据ID查询用户")
public UserVO queryUserById(@ApiParam(value = "用户ID", required = true) @PathVariable("id") Long id) {
// 调用服务层方法根据ID获取用户
User user = userService.getById(id);
// 将User实体对象转换为VO对象
return BeanUtil.copyProperties(user, UserVO.class);
}

/**
* 根据ID集合批量查询用户接口
*
* @param ids 用户ID集合
* @return 包含用户信息的VO对象列表
*/
@GetMapping
@ApiOperation("根据ID集合批量查询用户")
public List<UserVO> queryUserByIds(@ApiParam(value = "用户ID集合", required = true) @RequestParam("ids") List<Long> ids) {
// 调用服务层方法根据ID集合获取用户列表
List<User> users = userService.listByIds(ids);
// 将User实体对象列表转换为VO对象列表
return BeanUtil.copyToList(users, UserVO.class);
}
}

如果这个世界总是这么简单就好了。
当我们碰到复杂业务需求时,需自定义Service方法以融入业务逻辑,并在必要时通过自定义Mapper执行特定SQL语句,实现精准数据库操作。
这里我们以 根据id扣减余额这 一业务来举例。
该业务的复杂性体现在:在获取到用户ID后,首先需要验证用户的状态(status)是否处于正常状态,同时还需要检查用户的余额(balance)是否足够进行扣减。此外,在进行扣减时,要完成“update balance = balance - xxx”的SQL语句,这一操作不建议在业务层完成,最好通过自定义sql语句在数据库层完成。

1
2
3
4
5
6
7
@PutMapping("/{id}/deduction/{money}")
@ApiOperation("扣减用户余额接口")
public void deductBalance(
@ApiParam("用户id") @PathVariable("id") Long id,
@ApiParam("扣减金额") @PathVariable("money") Integer money){
userService.deductBalance(id, money);
}
1
2
3
4
5
6
/**
* 扣减余额
* @param id
* @param money
*/
void deductBalance(Long id, Integer money);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 扣减余额
* @param id
* @param money
*/
public void deductBalance(Long id, Integer money) {
//1、查询用户
User user = getById(id);
//2、校验用户状态
//使用反向校验,可以避免if嵌套,使代码更优雅
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常");
}
//3、查询用户余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足");
}
//4、扣减余额
baseMapper.deductBalance(id, money);
}
1
2
3
4
5
6
7
/**
* 扣减余额
* @param id
* @param money
*/
@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductBalance(@Param("id") Long id, @Param("money") Integer money);

完成后可以打开swagger文档,使用调试功能测试一下上述接口是否正常运行

IService的Lambda操作

查询

需求:实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name:用户名关键字,可以为空
  • status:用户状态,可以为空
  • minBalance:最小余额,可以为空
  • maxBalance:最大余额,可以为空

首先来新建一个名为UserQuery的实体类,封装用户查询时的各种条件。

  • 原因:一般在处理具有多个可选查询参数的复杂查询时,UserQuery类作为一个数据传输对象(DTO),能够清晰地组织和传递这些查询条件,从而使代码更加模块化和易于维护。
1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}

1
2
3
4
5
6
7
8
@GetMapping("/list")
@ApiOperation("根据复杂条件查询用户id")
public List<UserVO> queryUsers(UserQuery query){
//1、查询用户PO
List<User> users = userService.queryUsers(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
//2、将PO转换为VO
return BeanUtil.copyToList(users, UserVO.class);
}
1
2
3
4
5
6
7
8
9
/**
*根据复杂条件查询用户id
* @param name
* @param status
* @param minBalance
* @param maxBalance
* @return
*/
List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="queryUsers" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user
<where>
<if test="name != null">
AND username LIKE #{name})
</if>
<if test="status != null">
AND `status` = #{status}
</if>
<if test="minBalance != null and maxBalance != null">
AND balance BETWEEN #{minBalance} AND #{maxBalance}
</if>
</where>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 根据复杂条件查询用户id
* @param name
* @param status
* @param minBalance
* @param maxBalance
* @return
*/
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
return lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
}

更新

需求:改造根据id修改用户余额的接口,要求如下

  • 完成对用户状态校验
  • 完成对用户余额校验
  • 如果扣减后余额为0,则将用户status修改为冻结状态(2)

我们可以对2.3.1的最后一个例子的ServiceImpl代码进行改造,主要是第四步扣减余额以后,再对余额进行判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void deductBalance(Long id, Integer money) {
//1、查询用户
User user = getById(id);
//2、校验用户状态
//使用反向校验,可以避免if嵌套,使代码更优雅
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常");
}
//3、查询用户余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足");
}
//4、扣减余额,然后判断余额是否为0
int remainBlance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBlance)
.set(remainBlance == 0, User::getStatus, 2)
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance())
.update();
}

IService批量新增

需求:批量插入10万条用户数据,并作出对比:

  • 普通for循环插入
  • IService的批量插入
  • 开启rewriteBatchedStatements=true参数后,再使用IService的批量插入
1
2
3
4
5
6
7
8
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));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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));
}
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(UserInfo.of(24, "英文老师", "female"));
user.setInfo("{\"age\": 22, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(user.getCreateTime());
return user;
}
1
2
3
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true

运行后发现哪个性能最好呢?

两短一长选最长 当然是第三种方案性能最好啦

批处理方案:

  • 普通for循环逐条插入速度极差,不推荐
  • MP的批量新增,基于预编译的批处理,性能不错
  • 配置jdbc参数,开启rewriteBatchedStatements,性能最好

扩展功能

代码生成器

食用步骤:

  • 下载并安装插件MybatisX(记得重启IDEA哦)
  • 连接数据库
  • 找到希望生成代码的表,右键点击该表,然后在弹出的菜单中选择“MybatisX-Generate”选项,按需配置
  • 点击“生成”按钮,MybatisX插件将根据选择自动生成相应的代码文件

DB静态工具

需求:
① 改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址
② 改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址
③ 实现根据用户id查询收货地址功能,需要验证用户状态,冻结用户抛出异常(练习)

  • 需求①
1
2
3
4
5
@GetMapping("{id}")
@ApiOperation("根据id查询用户及地址接口")
public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){
return userService.queryUserAndAddressById(id);
}
1
UserVO queryUserAndAddressById(Long id);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public UserVO queryUserAndAddressById(Long id) {
//1、查询用户
User user = getById(id);
if(user == null || user.getStatus() == 2){
throw new RuntimeException("用户不存在");
}
//2、查询地址
List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, id).list();
//3、封装VO
//3.1 转user的PO为VO
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
//3.2 转地址VO
if (CollUtil.isNotEmpty(addresses)){
userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
}
return userVO;
}

需求②

1
2
3
4
5
6
7
8
@GetMapping("/list")
@ApiOperation("根据复杂条件查询用户id")
public List<UserVO> queryUsers(UserQuery query){
//1、查询用户PO
List<User> users = userService.queryUsers(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
//2、将PO转换为VO
return BeanUtil.copyToList(users, UserVO.class);
}
1
List<UserVO> queryUserAndAddressByIds(List<Long> ids);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
//1、查询用户id列表
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;
}

逻辑删除

逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据,只是在查询时查询不到了。思路如下:

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

在application.yaml中更改配置

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

运行下面的案例:

1
2
3
4
5
6
7
void testLogicDelete() {
//1、删除
addressService.removeById(59L);
//2、查询
Address address = addressService.getById(59L);
System.out.println("address=" + address);
}

运行后我们发现,删除操作执行后,再去查询这个数据时,结果为null,但是在数据库表中仍然存在,只不过deleted值变为1

枚举处理器

在application.yaml中配置全局枚举处理器:

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

在定义枚举时,为确保PO类中的枚举变量能与数据库字段正确映射,我们常用注解标记枚举实例的数据库对应值。@EnumValue注解用于指明枚举中的value字段,代表数据库中的整数值。而@JsonValue则用于说明序列化时的返回值类型。以下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FROZEN(2, "冻结"),
;
@EnumValue
private final int value;
@JsonValue
private final String desc;

UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}

Json处理器

MyBatisPlus中的Json处理器主要指的是一系列能够处理JSON数据类型与Java对象之间相互转换的工具或组件。这些处理器使得开发者能够方便地在数据库中存储和读取JSON格式的数据,并将其映射为Java对象,从而简化了数据访问层的开发。

数据转换:

  • 序列化:将Java对象转换为JSON字符串,以便存储到数据库中
  • 反序列化:将数据库中的JSON字符串读取并转换为Java对象,以便在应用程序中使用

MyBatisPlus提供了多种内置的Json处理器,以满足不同的需求。这些处理器包括但不限于:

  • JacksonTypeHandler:使用Jackson库进行JSON的序列化和反序列化
  • FastjsonTypeHandler:使用Fastjson库进行JSON的序列化和反序列化
  • GsonTypeHandler:使用Gson库进行JSON的序列化和反序列化

如果需要处理的字段能直接映射到这些标准处理器,那么就无需自定义类型处理器。然而,当需要处理复杂数据结构、实现自定义转换逻辑,或者为了保持类型安全和代码清晰性时,就需要自定义一个处理器。
练习时间对上文添加用户的info属性进行序列化和反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//配置一个MyBatis的配置自定义器,用于注册自定义的类型处理器。
@Bean
public ConfigurationCustomizer configurationCustomizer() {
// 返回一个Lambda表达式,该表达式实现了ConfigurationCustomizer接口
return configuration -> {
// 获取MyBatis的类型处理器注册表
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

// 注册自定义的类型处理器
// 将UserInfo.class(Java类型)与UserInfoTypeHandler(类型处理器实例)关联起来
// 当MyBatis遇到UserInfo类型的字段时,将使用这个类型处理器进行转换
typeHandlerRegistry.register(UserInfo.class, new UserInfoTypeHandler());
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 自定义类型处理器,用于处理UserInfo对象的序列化和反序列化。
* 当MyBatis与数据库交互时,此处理器负责将UserInfo对象转换为JSON字符串进行存储,以及从数据库中读取JSON字符串并将其转换回UserInfo对象。
*/
public class UserInfoTypeHandler extends BaseTypeHandler<UserInfo> {

// 设置非空参数
// 当MyBatis执行插入或更新操作时,会调用此方法将UserInfo对象->JSON字符串,并将其设置到PreparedStatement的指定位置
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, UserInfo userInfo, JdbcType jdbcType) throws SQLException {
// 将UserInfo对象转换为JSON字符串,并设置到预处理语句的指定位置。
preparedStatement.setString(i, JSONObject.toJSONString(userInfo));
}

// 从ResultSet中获取可空的结果
// 当MyBatis执行查询操作时,如果查询结果包含UserInfo对象的JSON表示,则会调用此方法将JSON字符串->UserInfo对象
@Override
public UserInfo getNullableResult(ResultSet resultSet, String s) throws SQLException {
// 从结果集中获取指定列名的JSON字符串,并将其转换为UserInfo对象。
String json = resultSet.getString(s);
return JSONObject.parseObject(json, UserInfo.class);
}


//从ResultSet中获取可空的结果(通过列索引)。功能与上一个方法类似,但此方法是通过列的索引来获取结果
@Override
public UserInfo getNullableResult(ResultSet resultSet, int i) throws SQLException {
// 从结果集中获取指定索引的JSON字符串,并将其转换为UserInfo对象。
String json = resultSet.getString(i);
return JSONObject.parseObject(json, UserInfo.class);
}

//从CallableStatement中获取可空的结果
//当MyBatis执行存储过程调用时,如果存储过程的返回结果包含UserInfo对象的JSON表示,则会调用此方法将JSON字符串转换为UserInfo对象
@Override
public UserInfo getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
// 从可调用语句中获取指定索引的JSON字符串,并将其转换为UserInfo对象。
String json = callableStatement.getString(i);
return JSONObject.parseObject(json, UserInfo.class);
}
}
1
2
3
4
5
6
7
8
@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
1
2
@TableField(typeHandler = JacksonTypeHandler.class, value = "info")
private UserInfo info;
1
2
@ApiModelProperty("详细信息")
private UserInfo info;
1
user.setInfo(UserInfo.of(24, "英文老师", "female"));

插件功能

MP提供了多个开箱即用的插件,通常建议的配置顺序是:多租户、动态表名、分页、乐观锁、SQL性能规范、防止全表更新与删除

这类插件使用的一般流程为:

  • 添加依赖:在项目的Maven或Gradle配置文件中添加MyBatis-Plus的依赖
  • 配置插件:在Spring Boot的配置类中添加插件配置。
    例如,配置分页插件时,需要创建MybatisPlusInterceptor对象,并添加PaginationInnerInterceptor到拦截器链中。
  • 使用插件:在Mapper接口或Service层中使用MP插件提供的功能。
    例如,使用分页插件时,可以调用selectPage方法进行分页查询。

分页插件基本用法

MP分页插件的主要作用是简化分页查询功能的实现,无需手动编写复杂的分页SQL语句,通过配置分页插件,我们可以轻松地获取指定页码和页大小的查询结果,同时获取总记录数和总页数等分页信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class MybatisConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1.创建分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(1000L);
// 2.添加分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
void testPageQuery() {
int pageNo = 1, pageSize = 2;
// 1.准备分页条件
// 1.1.分页条件
Page<User> page = Page.of(pageNo, pageSize);
// 1.2.排序条件 首先按balance字段降序排序,然后按id字段降序排序
page.addOrder(new OrderItem("balance", true));
page.addOrder(new OrderItem("id", true));

// 2.分页查询
Page<User> p = userService.page(page);

// 3.解析
long total = p.getTotal();
System.out.println("total = " + total);
long pages = p.getPages();
System.out.println("pages = " + pages);
List<User> users = p.getRecords();
users.forEach(System.out::println);
}

一些注释 这样写会不会更清晰呢
MybatisPlusInterceptor:MP提供的用于扩展MyBatis功能的拦截器集合
PaginationInnerInterceptor:MyBatis-Plus 3.4.0+版本用于处理分页逻辑的插件
DbType.MYSQL:指定了数据库类型为MySQL
setMaxLimit(1000L):设置最大单页限制数为1000,防止一次查询返回过多的数据导致内存溢出

通用分页实体

通过使用通用分页实体,不同的数据查询可以共享分页逻辑,减少重复代码。
练习时间
实现User的分页查询
如果排序字段为空,默认按照更新时间排序
排序字段不为空,则按照排序字段排序

1
2
3
4
5
6
7
8
9
10
11
12
@Data
@ApiModel(description = "分页查询条件实体")
public class PageQuery {
@ApiModelProperty("页码")
private Integer pageNo;
@ApiModelProperty("每页数量")
private Integer pageSize;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@ApiModel(description = "用户查询条件实体")
@EqualsAndHashCode(callSuper = true)
public class UserQuery extends PageQuery{
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
1
2
3
4
5
6
7
8
9
10
11
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<V> list;
}
1
2
3
4
5
@GetMapping("/page")
@ApiOperation("根据条件分页查询用户接口")
public PageDTO<UserVO> queryUserPage(UserQuery query){
return userService.queryUserPage(query);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public PageDTO<UserVO> queryUserPage(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
//1、构建分页条件
//1.1、分页条件
Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
//1.2、排序条件
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());
return dto;
}
//3.4、拷贝user的VO
List<UserVO> vos = BeanUtil.copyToList(records, UserVO.class);
dto.setList(vos);
//4、返回
return dto;
}

通用分页实体和MP转换

拿到query对象以后,构建分页条件,封装成对象,进行一大堆的封装操作是不是很麻烦呢?如果每次都这样写的话感觉笨笨的
其次就是拿到返回值结果后,将结果解析为dto对象的过程也很麻烦
事实上这部分代码通常与具体的业务逻辑没有紧密的耦合性
所以我们最好把它封装成工具,这个工具可以负责将分页查询条件转换成相应的实体对象,并简化获取和解析查询结果的过程,这样每次使用的时候只需要调用工具就好啦
Practice Time
①在PageQuery中定义方法,将PageQuery对象转为MyBatisPlus中的Page对象
②在PageDTO中定义方法,将MyBatisPlus中的Page结果转为PageDTO结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Data
@ApiModel(description = "分页查询条件实体")
public class PageQuery {

@ApiModelProperty("页码")
private Integer pageNo;

@ApiModelProperty("每页数量")
private Integer pageSize;

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

@ApiModelProperty("是否升序")
private Boolean isAsc;

public <T> Page<T> toMpPage(OrderItem ... orders){
// 1. 分页条件
Page<T> p = Page.of(pageNo, pageSize);

// 2. 排序条件
// 2.1. 先看前端有没有传排序字段(即sortBy是否为null)
if (sortBy != null) {
p.addOrder(new OrderItem(sortBy, isAsc));
return p;
}

// 2.2. 再看是否有手动指定的排序字段(即orders是否不为null且长度大于0)
if (orders != null && orders.length > 0) {
for (OrderItem order : orders) {
p.addOrder(order);
}
}

return p;
}

public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
// 调用重载的toMpPage方法,并传入一个新的OrderItem作为排序条件
return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
}


public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
// 调用重载的toMpPage方法,并传入默认的排序字段("create_time")和排序方向(降序,即false)
return toMpPage("create_time", false);
}

public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
// 调用重载的toMpPage方法,并传入默认的排序字段("update_time")和排序方向(降序,即false)
return toMpPage("update_time", false);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<V> list;

/**
* 返回空分页结果
* @param p MybatisPlus的分页结果
* @param <V> 目标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分页结果转为 VO分页结果
* @param p MybatisPlus的分页结果
* @param voClass 目标VO类型的字节码
* @param <V> 目标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) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = BeanUtil.copyToList(records, voClass);
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}

/**
* 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
* @param p MybatisPlus的分页结果
* @param convertor PO到VO的转换函数
* @param <V> 目标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) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public PageDTO<UserVO> queryUserPage(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
//1、构建分页条件
Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();
//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, UserVO.class);
}

完结撒花💖

这里的插件仅仅介绍了分页查询这一种的详细用法
其实在官方给出的文档中已经详细介绍了Mybatis-Plus各种插件的用法以及一些特性
在以后的学习过程中不妨试试通过文档来学习各种用法,链接在一开始就给出了哦
好啦,掰掰