业务层接口
- MybatisPlus不仅提供了
BaseMapper
接口,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService
,默认实现类为ServiceImpl
,其中封装的方法可以分为以下8类:save
:新增remove
:删除update
:更新get
:查询单个结果list
:查询集合结果count
:计数page
:分页查询lambda
快速入门
本项目名为MbPlusServiceDemo,已上传至Gitee,可自行下载
此处只进行不同步骤演示,具体搭建步骤可详见SpringBoot完整技术汇总
-
Step1: 创建一个SpringBoot整合MyBatisPlus的项目
MbPlusServiceDemo
注意:SpringBoot整合MyBatisPlus的项目与创建SpringBoot整合MyBatis的项目过程相同,区别就是:
整合MyBatisPlus项目只用添加一个MySQL Driver起步依赖即可,如图所示
因为后期在pom.xml文件中导入的MyBatisPlus坐标中包含了MyBatis的坐标
-
Step2: 创建数据库mp并在该库中创建表user 并使IDEA与数据库建立连接 ,SQL代码如下
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');
-
Step3: 在pom.xml文件中添加数据源坐标依赖(博主以Druid为例)、MyBatisPlus的坐标依赖、lombok坐标依赖
注意:若不添加,则会使用SpringBoot的默认数据源。此处根据实际情况来决定是否添加数据源。博主使用druid数据源
此处只给出相关的两个数据源坐标,可自行选择使用
<!--lombok坐标--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--druid坐标--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.18</version> </dependency> <!--c3p0坐标 <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.5</version> </dependency> --> <!--MyBatisPlus坐标--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.9</version> </dependency>
-
Step4: 将配置文件application.properties改为application.yml格式的配置文件,并在该配置文件中配置数据库连接信息、数据源信息以及MyBatisPlus的运行日志
注意:
配置MyBatisPlus的运行日志在此处写出来只是为了演示,后续可根据实际情况进行配置,在该快速入门中博主是进行该配置的。
spring: # 配置数据库连接信息以及数据源信息 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis-plus: configuration: # 配置MyBatisPlus的运行日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 实体类的别名扫描包---即设置别名 type-aliases-package: at.guigu.domain.po
-
Step5: 创建一个与三层架构包同级的domain包,并在该包下创建po包,然后在该po包下实体类
User
,代码如下package at.guigu.domain.po; import lombok.Data; import java.time.LocalDateTime; @Data public class User { // 用户id private Long id; // 用户名 private String username; // 密码 private String password; // 注册手机号 private String phone; // 详细信息 private String info; // 使用状态(1正常 2冻结) private Integer status; // 账户余额 private Integer balance; // 创建时间 private LocalDateTime createTime; // 更新时间 private LocalDateTime updateTime; }
-
Step6: 创建持久层dao包,并在该包下创建继承
BaseMapper<T>
接口的UserDao
接口,且泛型为对应的实体类的名称注意:此时持久层代码就已完全实现,因为MyBatisPlus底层已经封装了一系列的增删改查语句,后续只需调用就可以了
package at.guigu.dao; import at.guigu.domain.po.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserDao extends BaseMapper<User> { }
-
Step7: 创建业务层service包
-
在该包下创建继承
IService<T>
接口的IUserService
接口package at.guigu.service; import at.guigu.domain.po.User; import com.baomidou.mybatisplus.extension.service.IService; public interface IUserService extends IService<User> { }
-
在该包下创建子包impl,并在子包下创建继承
ServiceImpl<M extends BaseMapper<T>, T>
类、实现IUserService
接口的类UserServiceImpl
,同时用@Service
注解标注该类ServiceImpl<M extends BaseMapper<T>, T>
泛型中的第一个参数为自己定义的持久层接口,第二个参数为对应的实体类package at.guigu.service.impl; import at.guigu.dao.UserDao; import at.guigu.domain.po.User; import at.guigu.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl<UserDao, User> implements IUserService { }
-
需求示例
需求:基于RestFul风格实现CURD
-
Step1: 在测试目录test下创建测试包
at.guigu.service.impl
,并在impl包下创建测试类UserServiceImpl
,初始代码如下:创建测试类时可以利用
Alt+Enter
快速在测试目录下创建对应业务层实现类的测试类package at.guigu.service.impl; import at.guigu.domain.po.User; import at.guigu.service.IUserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.time.LocalDateTime; @SpringBootTest class UserServiceImplTest { @Autowired private IUserService userService; }
示例1: 新增用户
package at.guigu.service.impl;
import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
@SpringBootTest
class UserServiceImplTest {
@Autowired
private IUserService userService;
//新增用户
@Test
void test1() {
//模拟前端传来的数据
User user = new User();
user.setUsername("LiLei");
user.setPassword("123456");
user.setPhone("17736789882");
user.setBalance(200);
user.setInfo("{\"age\": 20, \"intro\": \"优秀小子\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
// 新增用户
userService.saveOrUpdate(user);
}
}
示例2: 根据id删除用户
package at.guigu.service.impl;
import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
@SpringBootTest
class UserServiceImplTest {
@Autowired
private IUserService userService;
// 根据id删除用户
@Test
void test2() {
List ids = List.of(1873307622315921410L);
userService.removeByIds(ids);
}
}
示例3: 根据id查询用户
package at.guigu.service.impl;
import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
@SpringBootTest
class UserServiceImplTest {
@Autowired
private IUserService userService;
// 根据用户id查询用户
@Test
void test3() {
Long id = 1L;
User user = userService.getById(id);
System.out.println(user);
}
}
示例4: 根据用户id集合查询用户
package at.guigu.service.impl;
import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
@SpringBootTest
class UserServiceImplTest {
@Autowired
private IUserService userService;
// 根据用户id集合查询用户
@Test
void test4() {
List ids = List.of(1L, 2L, 3L);
List users = userService.listByIds(ids);
users.forEach(System.out::println);
}
}
示例5:查询名字中带a的用户
package at.guigu.service.impl;
import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
@SpringBootTest
class UserServiceImplTest {
@Autowired
private IUserService userService;
// 查询名字中带a的用户
@Test
void test5() {
List<User> users = userService.list(new LambdaQueryWrapper<User>()
.like(User::getUsername, "a"));
users.forEach(System.out::println);
}
}
批量数据处理
- 有三种方式
- 方式一:普通for循环(不推荐)
- 方式二:IService批量处理
- 方式三:开启
rewriteBatchedStatements
参数 - 以上三种方式中,不推荐使用方式一,性能太垃圾,方式二和方式三联合使用时性能最高
需求:批量插入十万条用户数据
package at.guigu.service.impl;
import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class UserServiceImplTestTwo {
@Autowired
private IUserService userService;
private User InsertUser(int i) {
User user = new User();
user.setUsername("user_" + 1);
user.setPassword("123456");
user.setPhone("17736789882");
user.setBalance(200);
user.setInfo("{\"age\": 20, \"intro\": \"优秀小子\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
return user;
}
// 批量插入十万条用户数据
void test() {
List<User> userList = new ArrayList<User>(1000);
for (int i = 0; i < 10000; i++) {
userList.add(InsertUser(i));
if (i % 1000 == 0) {
userService.saveBatch(userList);
userList.clear();
}
}
}
}
-
MyBatisPlus的批量数据处理效率很高,原因是
-
MyBatisPlus的批量数据处理是基于PrepareStatement的预编译模式,然后批量提交。
-
但是这种方法仍有提升空间,因为它在数据库执行时还是会有多条insert语句,然后逐条插入数据,如下所示:
Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) Parameters: user_1, 123, 18688190001, "", 2000, 2023-12-01, 2023-12-01 Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) Parameters: user_2, 123, 18688190002, "", 2000, 2023-12-01, 2023-12-01 ...
-
-
因此为使批量数据处理效率更高,则在配置文件application.yml中,在数据源配置的url的属性值最后添加参数
&rewriteBatchedStatements=true
,此时配置文件代码如下:spring: # 配置数据库连接信息以及数据源信息 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true username: root password: 123456 mybatis-plus: configuration: # 配置MyBatisPlus的运行日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 实体类的别名扫描包---即设置别名 type-aliases-package: at.guigu.domain.po
此时就会将多条SL语句合并为一条,如下所示:
INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES (user_1, 123, 18688190001, "", 2000, 2023-12-01, 2023-12-01), (user_2, 123, 18688190002, "", 2000, 2023-12-01, 2023-12-01), (user_3, 123, 18688190003, "", 2000, 2023-12-01, 2023-12-01), (user_4, 123, 18688190004, "", 2000, 2023-12-01, 2023-12-01);
-
业务层的相关接口
IService<T>
接口
IService
接口相关方法说明
-
新增
save
是新增单个元素saveBatch
是批量新增saveOrUpdate
是根据id判断,如果数据存在就更新,不存在则新增saveOrUpdateBatch
是批量的新增或修改
-
删除
-
removeById
:根据id删除 -
removeByIds
:根据id批量删除 -
removeByMap
:根据Map中的键值对为条件删除 -
remove(Wrapper)
:根据Wrapper条件删除
-
-
修改
updateById
:根据id修改update(Wrapper)
:根据UpdateWrapper
修改,Wrapper
中包含set
和where
部分update(T,Wrapper)
:按照T
内的数据修改与Wrapper
匹配到的数据updateBatchById
:根据id批量修改
-
Get
getById
:根据id查询1条数据getOne(Wrapper)
:根据Wrapper
查询1条数据getBaseMapper
:获取Service
内的BaseMapper
实现,某些时候需要直接调用Mapper
内的自定义SQL
时可以用这个方法获取到Mapper
-
List
listByIds
:根据id批量查询list(Wrapper)
:根据Wrapper条件查询多条数据list()
:查询所有
-
Count
count()
:统计所有数量count(Wrapper)
:统计符合Wrapper
条件的数据数量
-
getBaseMapper
当我们在service中要调用Mapper中自定义SQL时,就必须获取service对应的Mapper,就可以通过这个方法:
IRepository<T>
接口
-
IRepository<T>
接口是IService<T>
接口的父接口,里面定义了所有的增删改查的方法 -
IRepository<T>
接口中的几个特殊方法方法 解释 default QueryChainWrapper<T> query()
相当于 QueryWrapper
default LambdaQueryChainWrapper<T> lambdaQuery()
相当于 LambdaQueryWrapper
default LambdaQueryChainWrapper<T> lambdaQuery(T entity)
default UpdateChainWrapper<T> update()
相当于 LambdaQueryWrapper
default LambdaUpdateChainWrapper<T> lambdaUpdate()
相当于 LambdaQueryWrapper
lambdaQuery()
方法的需求示例
示例1:查询名字为Rose的用户(假设名为Rose的只有一个)
/**
* `lambdaQuery()`方法的需求示例
*/
@SpringBootTest
class UserServiceImplTestThree {
@Autowired
private IUserService userService;
// 查询名字为Rose的用户(假设名为Rose的只有一个)
@Test
void test1() {
User user = userService.lambdaQuery()
.eq(User::getUsername, "Rose")
.one();// one()代表结果只有一个
System.out.println(user);
}
}
示例2:查询名字中含a的用户
/**
* `lambdaQuery()`方法的需求示例
*/
@SpringBootTest
class UserServiceImplTestThree {
@Autowired
private IUserService userService;
// 查询名字中含a的用户
@Test
void test2() {
List<User> users = userService.lambdaQuery()
.like(User::getUsername, "a")
.list();// list()代表结果有多个
users.forEach(System.out::println);
}
}
示例3:统计名字中含o的用户数量
/**
* `lambdaQuery()`方法的需求示例
*/
@SpringBootTest
class UserServiceImplTestThree {
@Autowired
private IUserService userService;
// 统计名字中含o的用户数量
@Test
void test3() {
Long nums = userService.lambdaQuery()
.eq(User::getUsername, "o")
.count();
System.out.println("名字中含o的用户数量为:" + nums);
}
}
示例4:定义一个方法,接收参数为username、status、minBalance、maxBalance,参数可以为空,此时:
若username不为空,则采用模糊查询
若status不为空,则采用精确匹配
若minBalance不为空,则余额必须大于minBalance
若maxBalance不为空,则余额必须小于maxBalance
/**
* `lambdaQuery()`方法的需求示例
*/
@SpringBootTest
class UserServiceImplTestThree {
@Autowired
private IUserService userService;
/**
* 定义一个方法,接收参数为username、status、minBalance、maxBalance,参数可以为空,此时:
*若username不为空,则采用模糊查询
*若status不为空,则采用精确匹配
*若minBalance不为空,则余额必须大于minBalance
*若maxBalance不为空,则余额必须小于maxBalance
* @param username
* @param status
* @param min
* @param max
* @return List<User>
*/
public List<User> test4(String username, Integer status, Long min, Long max) {
return userService.lambdaQuery()
.like(username != null, User::getUsername, username)
.eq(status != null, User::getStatus, status)
.gt(min != null, User::getBalance, min)
.lt(max != null, User::getBalance, max)
.list();
}
}
lambdaUpdate()
方法的需求示例
基于IService的
lambdaUpdate()
方法实现一个更新方法,满足以下需求: 参数为id、username、balance
id或username至少一个不为空,根据id或username精确匹配用户
将匹配到的用户的余额修改为balance
若balance为0,则将用户的status修改为冻结状态2
package at.guigu.service.impl;
import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* `lambdaUpdate()`方法的需求示例
*/
@SpringBootTest
class UserServiceImplTestFour {
@Autowired
private IUserService userService;
/**
* 基于IService的`lambdaUpdate()`方法实现一个更新方法,满足以下需求:
* 参数为id、username、balance
* id或username至少一个不为空,根据id或username精确匹配用户
* 将匹配到的用户的余额修改为balance
* 若balance为0,则将用户的status修改为冻结状态2
* @param username
* @param id
* @param username
* @param balance
* @return List<User>
*/
void test1(Long id, String username, Integer balance) {
if (id == null && username == null) {
throw new RuntimeException("id或username至少一个不为空");
}
userService.lambdaUpdate()
.set(User::getBalance, balance)
.set(balance == 0, User::getStatus, 2)
.eq(id != null, User::getId, id)
.eq(username != null, User::getUsername, username)
.update(); // update()是用来执行更新方法,若不加该方法就无法更新
}
}
静态工具
-
背景引入
- 假设现在业务层有两个接口,且实现类分别为A和B。此时若A需要使用B,且B也要使用A(即Service之间进行相互调用),就需要用
@Autowired
注解进行依赖注入,这样就造成了循环依赖的问题 - 虽然Spring可以解决这样简单的循环依赖,但是对于多层嵌套的复杂循环依赖来说,Spring还是解决不了
- 这就会导致问题出现,因此就有两种解决方法
- 方法一:A用
@Autowired
注解依赖注入B对应的持久层接口的Bean;B用@Autowired
注解依赖注入A对应的持久层接口的Bean - 方法二:使用静态工具(本小节重点)
- 方法一:A用
- 假设现在业务层有两个接口,且实现类分别为A和B。此时若A需要使用B,且B也要使用A(即Service之间进行相互调用),就需要用
-
MyBatisPlus提供了一个静态工具类
Db
,可供我们解决复杂依赖注入问题。该类中的方法均为静态方法-
新增相关静态方法
-
删除相关静态方法
-
查询相关静态方法
-
count
-
lambda
-
page
-
-
该静态工具类
Db
与IService接口的用法一模一样,区别就是:- 静态工具类中的有些方法要传入要操作的表所对应的po实体类(这就是解决循环依赖的关键)
- 使用该静态工具类时,不需要使用
@Autowired
进行依赖注入,因为它传入了要操作的表所对应的po实体类
-
注意
- 一般在业务内部若不需要使用另一个持久层对应的接口,则尽量使用
IService
中的方法 - 若在业务内部需要使用另一个持久层对应的接口,此时为了避免循环依赖,则使用
BaseMapper
或静态工具类Db
- 一般在业务内部若不需要使用另一个持久层对应的接口,则尽量使用
拓展功能
本项目
MpTuoDemo
已上传至Gitee,可自行下载
代码自动生成
-
背景引入
-
在一个项目中,持久层、业务层、po层等基础代码是固定不变的(如图所示),如果均交给人工就太浪费时间,所以就有了代码自动生成来简化开发
-
-
作用:主要用于 自动生成实体类、Mapper 接口、XML 配置文件、Service 层及其实现类 等,减少了大量的重复性编码工作,提升了开发效率
-
Step1: 新建一个空白项目
MpTuoDemo
,然后在idea中安装插件MyBatisPlus -
Step2: 按图示操作连接数据库
-
Step3: 按图示操作生成对应的包以及代码
完成后如下图所示
枚举处理器
- 背景引入
- 在实体类中一般都存在状态字段,且在实际项目中状态字段的值很多,不可能记得过来,这就会导致效率低下,出错率高。因此我们就可以通过定义一个枚举类来解决该问题
@EnumValue
:指定枚举值在数据库中存储的实际值- 即枚举类的其它属性被标记为
@EnumValue
时,处理器会自动识别这个属性,然后- 在添加数据时,MyBatisPlus会自动将枚举类型所对应的该属性的值写到数据库表的对应字段中
- 在查询数据时,MyBatisPlus会自动根据数据库表的对应的字段的值来返回对应的枚举类型
- 即枚举类的其它属性被标记为
快速入门
本示例以User实体类中的状态字段
status
为例
-
Step1: 创建一个与三层架构包同级的enums枚举包,并在该包下创建利用enum关键字声明的枚举类
UserStatus
,代码如下枚举类的创建可详见Javase基础部分内容
-
Step1-1: 定义枚举类本类主要属性(属性之间用逗号隔开,最后用分号结束)
-
Step1-2: 定义枚举类其它普通属性
vlaue
和desc
- 其中
vlaue
需要加上@EnumValue
注解
- 其中
-
Step1-3: 枚举类中普通属性通过构造器赋值
-
Step1-4: 给枚举类添加
@AllArgsConstructor
和@Getter
注解-
@AllArgsConstructor
:自动生成一个包含类中所有字段的构造函数注意:在枚举类中不包含枚举类本类主要属性,只包括其它普通属性。若不加该注解则必须通过私有的有参构造器来给其它普通属性赋值。
博主此处演示了未加该注解的方法
-
package at.guigu.enums; import com.baomidou.mybatisplus.annotation.EnumValue; import lombok.Getter; @Getter public enum UserStatus { // 定义枚举类本类主要属性 FROZEN(0, "禁止使用"), NORMAL(1, "已激活"), ; // 定义枚举类其它普通属性 @EnumValue private int value; private String desc; // 枚举类中普通属性通过构造器赋值(由于未加@AllArgsConstructor,所以必须利用有参构造器给其它属性赋值) private UserStatus(Integer value, String desc) { this.value = value; this.desc = desc; } }
-
-
Step2: 更改实体类User中属性status的数据类型为枚举类
-
Step3: 在配置文件中配置默认的枚举类型处理器 (
default-enum-type-handler
),完整配置文件application.yml代码如下spring: # 配置数据库连接信息以及数据源信息 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true username: root password: 123456 mybatis-plus: configuration: # 配置MyBatisPlus的运行日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置默认的枚举类型处理器 default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler # 实体类的别名扫描包---即设置别名 type-aliases-package: at.guigu.domain.po
-
Step4: 代码测试—新增用户
/** * 测试枚举处理器 */ @SpringBootTest class UserServiceImplTest { @Autowired private IUserService userService; // 新增用户 @Test void test1() { //模拟前端传来的数据 User user = new User(); user.setUsername("LiLei"); user.setPassword("123456"); user.setPhone("17736789882"); user.setBalance(200); user.setInfo("{\"age\": 20, \"intro\": \"优秀小子\", \"gender\": \"female\"}"); user.setCreateTime(LocalDateTime.now()); user.setUpdateTime(LocalDateTime.now()); user.setStatus(UserStatus.NORMAL); // 新增用户 userService.saveOrUpdate(user); } }
-
Step5: 代码测试—根据用户id查询用户
/** * 测试枚举处理器 */ @SpringBootTest class UserServiceImplTest { @Autowired private IUserService userService; // 根据用户id查询用户 @Test void test2() { Long id = 1L; User user = userService.getById(id); System.out.println(user); } }
注意
-
若不给枚举类
UserStatus
中的对应的属性添加@EnumValue
注解,则此时UserStatus
代码如下:-
Step1: 让枚举类
UserStatus
实现IEnum<T>
接口,且泛型为Integer
-
Step2: 定义枚举类本类主要属性(属性之间用逗号隔开,最后用分号结束)
-
Step3: 定义枚举类其它普通属性
vlaue
和desc
-
Step4: 枚举类中普通属性通过构造器赋值
-
Step4-1: 给枚举类添加
@AllArgsConstructor
和@Getter
注解-
@AllArgsConstructor
:自动生成一个包含类中所有字段的构造函数注意:在枚举类中不包含枚举类本类主要属性,只包括其它普通属性。若不加该注解则必须通过私有的有参构造器来给其它普通属性赋值。
博主此处演示了未加该注解的方法
-
-
-
Step5: 重写IEnum接口中的getter方法
- 该方法返回数据库表的字段所对应的那个其它普通属性(相当于代替了
@EnumValue
注解)
- 该方法返回数据库表的字段所对应的那个其它普通属性(相当于代替了
package at.guigu.enums; import com.baomidou.mybatisplus.annotation.IEnum; import lombok.Getter; @Getter public enum UserStatus implements IEnum<Integer> { // 定义枚举类本类主要属性 FROZEN(0, "禁止使用"), NORMAL(1, "已激活"), ; // 定义枚举类其它普通属性 private int value; private String desc; // 枚举类中普通属性通过构造器赋值 private UserStatus(Integer value, String desc) { this.value = value; this.desc = desc; } // 重写IEnum接口中的getter方法 @Override public Integer getValue() { return this.value; } }
-
-
在快速入门的运行截图中可看出status返回的是枚举实例的名称即
status=NORMAL
,在实际项目开发中,可能要求返回的是枚举实例名称的详细说明所对应的普通属性的属性值,即status=已激活
,此时代码更改如下:-
枚举类
UserStatus
未实现IEnum<T>
接口: 给对枚举实例进行解释说明的普通属性添加@JsonValue
解package at.guigu.enums; import com.baomidou.mybatisplus.annotation.EnumValue; import lombok.Getter; @Getter public enum UserStatus { // 定义枚举类本类主要属性 FROZEN(0, "禁止使用"), NORMAL(1, "已激活"), ; // 定义枚举类其它普通属性 @EnumValue private int value; @JsonValue private String desc; // 枚举类中普通属性通过构造器赋值(由于未加@AllArgsConstructor,所以必须利用有参构造器给其它属性赋值) private UserStatus(Integer value, String desc) { this.value = value; this.desc = desc; } }
-
枚举类
UserStatus
实现IEnum<T>
接口: 重写toString
方法package at.guigu.enums; import com.baomidou.mybatisplus.annotation.IEnum; import lombok.Getter; @Getter public enum UserStatus implements IEnum<Integer> { // 定义枚举类本类主要属性 FROZEN(0, "禁止使用"), NORMAL(1, "已激活"), ; // 定义枚举类其它普通属性 private int value; private String desc; // 枚举类中普通属性通过构造器赋值 private UserStatus(Integer value, String desc) { this.value = value; this.desc = desc; } // 重写IEnum接口中的getter方法 @Override public Integer getValue() { return this.value; } // 重写toString方法 @Override public String toString() { return this.desc; // 返回 desc 属性值 } }
-
JSON处理器
-
背景引入
-
在数据库的user表中有一个info字段为JSON类型,格式为:
{"age": 20, "intro": "佛系青年", "gender": "male"}
{ "age": 20, "intro": "佛系青年", "gender": "male" }
-
但是在其对应的实体类中是
String
类型,如图所示 -
由于类型不一样,所以在读取info的属性值时就很不方便。如果要方便获取,info的类型最好是一个
Map
或者实体类。而一旦我们把info
改为对象
类型,就需要在写入数据库时手动转为String
,再读取数据库时,手动转换为对象
,这会非常麻烦。 -
因此MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用
JacksonTypeHandler
处理器。
-
快速入门
-
Step1: 在pojo包下创建
UserInfo
类,代码如下:@NoArgsConstructor
:自动生成一个无参构造函数@AllArgsConstructor(staticName = "fo")
:自动生成一个包含类中所有字段的构造函数,并且利用staticName属性来为该有参构造器设置一个静态方法名,以供方便后续调用
package at.guigu.domain.po; import lombok.Data; @Data @NoArgsConstructor @AllArgsConstructor(staticName = "fo") public class UserInfo { //年龄 private Integer age; //简介 private String intro; //性别 private String gender; }
-
Step2: 将实体类User中的info属性的类型由
String
改为UserInfo
-
Step3: 继续更改User实体类代码
- 给实体类中info属性添加
@TableField
注解,并给该注解添加JSON的类型处理器,代码如下: - 给实体类的
@TableName
注解添加autoResultMap
属性,且值设为true
autoResultMap
:是否自动构建 resultMap 并使用,默认为false
- 若不加该属性,则前端无法获取info值
package at.guigu.domain.po; import at.guigu.enums.UserStatus; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; import java.io.Serializable; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName(value = "user", autoResultMap = true) public class User implements Serializable { private static final long serialVersionUID = 1L; // 用户id @TableId(value = "id", type = IdType.AUTO) private Long id; // 用户名 private String username; // 密码 private String password; // 注册手机号 private String phone; // 详细信息 // 利用@TableField注解的typeHandler属性配置JSON的类型处理器 @TableField(typeHandler = JacksonTypeHandler.class) private UserInfo info; // 使用状态(1正常 2冻结) private UserStatus status; // 账户余额 private Integer balance; // 创建时间 private LocalDateTime createTime; // 更新时间 private LocalDateTime updateTime; }
1
- 给实体类中info属性添加
-
Step4: 代码测试—新增用户
/** * 测试枚举处理器 */ @SpringBootTest class UserServiceImplTest { @Autowired private IUserService userService; //新增用户 @Test void test1() { //模拟前端传来的数据 User user = new User(); user.setUsername("LiMing"); user.setPassword("123456"); user.setPhone("17736789882"); user.setBalance(200); user.setCreateTime(LocalDateTime.now()); user.setUpdateTime(LocalDateTime.now()); user.setStatus(UserStatus.FROZEN); user.setInfo(UserInfo.fo(16, "Java老师", "female")); // 新增用户 userService.saveOrUpdate(user); } }
-
Step5: 代码测试—根据用户id查询用户
/** * 测试枚举处理器 */ @SpringBootTest class UserServiceImplTest { @Autowired private IUserService userService; // 根据用户id查询用户 @Test void test2() { Long id = 1L; User user = userService.getById(id); System.out.println(user); } }
配置加密
在实际项目中,配置文件中的用户名、密码、url等是不会直接暴露出来的,而是以一种加密的方式显示,让使用人员无法得知。
-
MyBatisPlus提供了一个基于AES算法的加密工具来对配置中的敏感信息进行加密处理
-
MyBatis-Plus 允许你使用加密后的字符串来配置数据库连接信息。在 YML 配置文件中,以
mpw:
开头的配置项将被视为加密内容,如下所示spring: datasource: url: mpw:qRhvCwF4GOqjessEB3G+a5okP+uXXr96wcucn2Pev6Bf1oEMZ1gVpPPhdDmjQqoM password: mpw:Hzy5iliJbwDHhjLs1L0j6w== username: mpw:Xb+EgsyuYRXw7U7sBJjBpA==
-
-
Step1: 在项目MpTuoDemo自带的测试类
MpTuoDemoApplicationTests
中编写代码package at.guigu; import com.baomidou.mybatisplus.core.toolkit.AES; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MpTuoDemoApplicationTests { @Test void contextLoads() { // 生成16位随机AES密钥 String randomKey = AES.generateRandomKey(); System.out.println("randomKey:" + randomKey); // 利用密码对用户名加密 String username = AES.encrypt("root", randomKey); System.out.println("username:" + username); // 利用密钥对密码进行加密 String password = AES.encrypt("123456", randomKey); System.out.println("password:" + password); } }
运行该测试类即可得出对应的密钥和密文
-
Step2: 将用户名、密码的密文分别复制到配置文件的对应位置处
注意:格式为:
mpw:密文
配置解密
-
若项目打包成jar包运行,则命令为:
java -jar 项目jar包名.jar --mpw.key=生成的随机16位密钥
-
若在idea的测试包test下单元测试时,则必须给
@SpringBootTest
注解添加属性args
,且属性值为"--mpw.key=生成的随机16位密钥"
-
若用idea的引导类启动该项目时,步骤如下:
Program arguments属性值为:
--mpw.key=生成的随机16位密钥
插件功能
-
MyBatisPlus基于MyBatis的Interceptor接口实现了一个基础拦截器,并在内部保存了MyBatisPlus的内置拦截器的集合
-
MyBatisPlus提供了6个内置拦截器
内置拦截器 解释 PaginationInnerInterceptor
自动分页 OptimisticLockerInnerInterceptor
乐观锁 DynamicTableNameInnerInterceptor
动态表名 BlockAttackInnerInterceptor
防止全表更新与删除 IllegalSQLInnerInterceptor
SQL 性能规范 TenantLineInnerInterceptor
多租户
分页插件
-
Step1: 在pom.xml文件中添加mybatis-plus-jsqlparser坐标依赖
博主使用的MyBatisPlus为3.5.9的版本,于
v3.5.9
起,PaginationInnerInterceptor
已分离出来。如需使用,则需单独引入mybatis-plus-jsqlparser
依赖;若使用3.5.9之前的版本则不需要导入以下依赖<!--mybatis-plus-jsqlparser--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> <version>3.5.9</version> </dependency>
-
Step2: 创建一个与三层架构包同级的
config
包并在该包下创建一个MyBatisPlus的配置类MpConfig
,代码如下- Step2-1: 在该类中创建一个使用
@Bean
注解的拦截器方法mybatisPlusInterceptor()
- Step2-2: 在该方法中定义MyBatisPlus的拦截器并添加具体的拦截器—分页拦截器
注意:
DbType
枚举类包括多个数据库类型,用于指定具体的数据库类型。当有多个数据库则可以不配具体类型,否则都建议配上具体的DbType在实际项目中,若要在MyBatis的配置类中添加多个插件方法(即定义多个bean),则分页对应的bean必须最后添加
package at.guigu.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("at.guigu.mapper") public class MpConfig { /** * 添加拦截器方法 * @return */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { // 定义MyBatisPlus的拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); // 配置分页拦截器---注意:若有多个数据源则可以不配具体类型,否则都建议配上具体的DbType(即数据库) mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return mpInterceptor; } }
- Step2-1: 在该类中创建一个使用
-
Step3: 创建测试类
PageTest
,代码如下package at.guigu.service.impl; import at.guigu.domain.po.User; import at.guigu.mapper.UserMapper; import at.guigu.service.IUserService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.Arrays; @SpringBootTest(args = "--mpw.key=hqWQFW3P0eTiPgbt") public class PageTest { @Autowired private IUserService userService; @Test public void test1() { // 构建分页条件 IPage<User> p = new Page<>(1, 2); // 根据指定参数进行升序排列 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.orderByAsc(User::getBalance); userService.page(p, wrapper); /*等同于 userService.lambdaQuery() .orderByAsc(User::getBalance) .page(p); */ System.out.println("当前页码值为:" + p.getCurrent()); System.out.println("每页显示的数据数量为:" + p.getSize()); System.out.println("总页数为" + p.getPages()); System.out.println("总数据数量为" + p.getTotal()); System.out.println("当前页的数据集合为:" + p.getRecords()); } @Test public void test2() { // 构建分页条件 Page<User> p = Page.of(1, 2); // 根据指定参数进行升序排列 page.addOrder(OrderItem.ascs("balance")); Page<User> p = userService.page(p, null);//等同于 userService.page(p, null); //注意:IUservice接口的page方法返回的p就是构建分页条件中的p System.out.println("当前页码值为:" + p.getCurrent()); System.out.println("每页显示的数据数量为:" + p.getSize()); System.out.println("总页数为" + p.getPages()); System.out.println("总数据数量为" + p.getTotal()); System.out.println("当前页的数据集合为:" + p.getRecords()); } }
-
测试类中写了两种分页方法
IPage<T>
接口中无排序的相关方法,必须使用Wrapper抽象类的子类来封装条件语句IPage<T>
接口的实现类Page<T>
中存在排序相关方法,则直接使用即可
案例
本项目MpDemo已上传至Gitee,可自行下载
需求:编写一个UserController接口,实现User的分页查询,要求如下:
-
返回结果—返回的为JSON对象
环境准备一
-
Step1: 创建一个SpringBoot整合MyBatisPlus的项目
MbPlusServiceDemo
注意:SpringBoot整合MyBatisPlus的项目与创建SpringBoot整合MyBatis的项目过程相同,区别就是:
整合MyBatisPlus项目只用添加一个MySQL Driver起步依赖即可,如图所示
因为后期在pom.xml文件中导入的MyBatisPlus坐标中包含了MyBatis的坐标
-
Step2: 创建数据库mp并在该库中创建表user 并使IDEA与数据库建立连接 ,SQL代码如下
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');
-
Step3: 配置pom.xml文件
-
添加数据源坐标依赖
- 数据源坐标:druid
- MyBatisPlus坐标:MyBatisPlus
- lombok坐标:lombok
- 分页坐标:mybatis-plus-jsqlparser
- hutool坐标:hutool-all
<!--lombok坐标--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--druid坐标--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.18</version> </dependency> <!--MyBatisPlus坐标--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.9</version> </dependency> <!--分页坐标mybatis-plus-jsqlparser--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> <version>3.5.9</version> </dependency> <!--hutool坐标--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.25</version> </dependency>
-
配置插件
- Mockito警告解决
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.2</version> <configuration> <argLine>-javaagent:"${settings.localRepository}/org/mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar"</argLine> </configuration> </plugin>
-
-
Step4: 利用代码生成器生成对应的包以及代码,初始项目结构如下
-
Step5: 将配置文件application.properties改为application.yml格式的配置文件,并在该配置文件中配置数据库连接信息、数据源信息以及MyBatisPlus的运行日志
注意:
配置MyBatisPlus的运行日志在此处写出来只是为了演示,后续可根据实际情况进行配置,在该快速入门中博主是进行该配置的。
spring: # 配置数据库连接信息以及数据源信息 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true username: root password: 123456 mybatis-plus: configuration: # 配置MyBatisPlus的运行日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 实体类的别名扫描包---即设置别名 type-aliases-package: at.guigu.domain.po
-
Step6: 在项目MpDemo自带的测试类
MpDemoApplicationTests
中编写代码,获取到对应的密钥和密文package at.guigu; import com.baomidou.mybatisplus.core.toolkit.AES; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MpDemoApplicationTests { @Test void contextLoads() { // 生成16位随机AES密钥 String randomKey = AES.generateRandomKey(); System.out.println("randomKey:" + randomKey); // 利用密码对用户名加密 String username = AES.encrypt("root", randomKey); System.out.println("username:" + username); // 利用密钥对密码进行加密 String password = AES.encrypt("123456", randomKey); System.out.println("password:" + password); } }
输出结果为
randomKey:eAomgIauKAmVZAmD username:d4k5Nxb42AqYk5W8w2Jq+A== password:58I7EgY9dVRmpDBECFNJWg==
-
Step7: 将配置文件中的用户名、密码加密
spring: # 配置数据库连接信息以及数据源信息 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: mpw:d4k5Nxb42AqYk5W8w2Jq+A== password: mpw:58I7EgY9dVRmpDBECFNJWg== mybatis-plus: configuration: # 配置MyBatisPlus的运行日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 实体类的别名扫描包---即设置别名 type-aliases-package: at.guigu.domain.po
-
Step8: 进行IDEA配置解密相关操作
步骤略,可详见配置解密部分内容
注意:单元测试以及引导类均需解密操作,否则无法运行
-
Step9: 创建一个与三层架构包同级的
config
包并在该包下创建一个MyBatisPlus的配置类MpConfig
,代码如下- Step9-1: 在该类中创建一个使用
@Bean
注解的拦截器方法mybatisPlusInterceptor()
- Step9-2: 在该方法中定义MyBatisPlus的拦截器并添加具体的拦截器—分页拦截器
注意:
DbType
枚举类包括多个数据库类型,用于指定具体的数据库类型。当有多个数据库则可以不配具体类型,否则都建议配上具体的DbType在实际项目中,若要在MyBatis的配置类中添加多个插件方法(即定义多个bean),则分页对应的bean必须最后添加
package at.guigu.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("at.guigu.mapper") public class MpConfig { /** * 添加拦截器方法 * @return */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { // 定义MyBatisPlus的拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); // 配置分页拦截器---注意:若有多个数据源则可以不配具体类型,否则都建议配上具体的DbType(即数据库) mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return mpInterceptor; } }
- Step9-1: 在该类中创建一个使用
-
Step10: 创建一个与三层架构包同级的enums枚举包,并在该包下创建利用enum关键字声明的枚举类
UserStatus
,代码如下:具体解释可详见枚举处理器部分内容
package at.guigu.enums; import com.baomidou.mybatisplus.annotation.EnumValue; import lombok.Getter; @Getter @AllArgsConstructor public enum UserStatus { // 定义枚举类本类主要属性 FROZEN(0, "禁止使用"), NORMAL(1, "已激活"), ; // 定义枚举类其它普通属性 @EnumValue private int value; private String desc; }
-
Step11: 更改实体类User中属性status的数据类型为枚举类
-
Step12: 在配置文件中配置默认的枚举类型处理器 (
default-enum-type-handler
),完整配置文件application.yml代码如下spring: # 配置数据库连接信息以及数据源信息 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true username: mpw:d4k5Nxb42AqYk5W8w2Jq+A== password: mpw:58I7EgY9dVRmpDBECFNJWg== mybatis-plus: configuration: # 配置MyBatisPlus的运行日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置默认的枚举类型处理器 default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler # 实体类的别名扫描包---即设置别名 type-aliases-package: at.guigu.domain.po
-
Step13: 在po包下创建
UserInfo
类,代码如下:具体解释可详见JSON处理器部分内容
package at.guigu.domain.po; import lombok.Data; @Data @NoArgsConstructor @AllArgsConstructor(staticName = "fo") public class UserInfo { //年龄 private Integer age; //简介 private String intro; //性别 private String gender; }
-
Step14: 将实体类User中的info属性的类型由
String
改为UserInfo
-
Step15: 继续更改User实体类代码
- 给实体类中info属性添加
@TableField
注解,并给该注解添加JSON的类型处理器,代码如下: - 给实体类的
@TableName
注解添加autoResultMap
属性,且值设为true
autoResultMap
:是否自动构建 resultMap 并使用,默认为false
package at.guigu.domain.po; import at.guigu.enums.UserStatus; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; import java.io.Serializable; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName(value = "user", autoResultMap = true) public class User implements Serializable { private static final long serialVersionUID = 1L; // 用户id @TableId(value = "id", type = IdType.AUTO) private Long id; // 用户名 private String username; // 密码 private String password; // 注册手机号 private String phone; // 详细信息 // 利用@TableField注解的typeHandler属性配置JSON的类型处理器 @TableField(typeHandler = JacksonTypeHandler.class) private UserInfo info; // 使用状态(1正常 2冻结) private UserStatus status; // 账户余额 private Integer balance; // 创建时间 private LocalDateTime createTime; // 更新时间 private LocalDateTime updateTime; }
- 给实体类中info属性添加
环境准备二
由于请求参数实体为JSON格式,所以需要定义一个请求参数实体
-
Step1: 在domain包下创建query包,并在该包下创建分页查询的请求参数实体
PageQuery
,代码如下注意:
前端的请求参数实体放在query包下;
写分页参数实体时,需要给定页码、每页数据量、是否升序排列默认值;
而需要排序的字段则由用户指定。
package at.guigu.domain.query; import lombok.Data; /** * 分页查询的请求参数实体 */ @Data public class PageQuery { // 当前页码 private Integer pageNo = 1; // 每页显示的数据量 private Integer pageSize = 5; // 定义需要排序的字段 private String sortBy; // 是否升序排列 private Boolean isAsc = false; }
由于返回结果返回的是JSON对象,所以需要定义一个返回结果实体 ,又因为list属性值为集合,集合中也是JSON对象。所以一共需要定义两个返回结果实体
-
Step1: 在domain包下创建vo包,并在该包下创建分页查询的返回结果实体类
PageVo
,代码如下注意:
返回给前端的结果实体放在vo包下;
package at.guigu.domain.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class PageVo<T> { private Long total; private Long Pages; private List<T> list; }
-
Step2: 在vo包下创建返回结果实体类UserVo,代码如下:
package at.guigu.domain.vo; import at.guigu.domain.po.UserInfo; import at.guigu.enums.UserStatus; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.Data; @Data public class UserVo { // 用户id private Integer id; // 用户名 private String username; /** * 详细信息 * 利用@TableField注解的typeHandler属性配置JSON的类型处理器 */ @TableField(typeHandler = JacksonTypeHandler.class) private UserInfo info; /** * 使用状态(1正常 2冻结) */ private UserStatus status; // 账户余额 private Integer balance; }
业务层
-
Step1: 在
IUserService
接口中写入分页查询的方法queryUserByPage
package at.guigu.service; import at.guigu.domain.po.User; import at.guigu.domain.query.PageQuery; import at.guigu.domain.vo.PageVo; import at.guigu.domain.vo.UserVo; import com.baomidou.mybatisplus.extension.service.IService; public interface IUserService extends IService<User> { // 分页查询方法 PageVo<UserVo> queryUserByPage(PageQuery pageQuery); }
-
Step2: 在
IUserService
接口的实现类UserServiceImpl
中重写分页查询方法queryUserByPage
package at.guigu.service.impl; import at.guigu.domain.po.User; import at.guigu.domain.query.PageQuery; import at.guigu.domain.vo.PageVo; import at.guigu.domain.vo.UserVo; import at.guigu.mapper.UserMapper; import at.guigu.service.IUserService; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; /** * <p> * 用户表 服务实现类 * </p> * * @author cgrs572 * @since 2024-12-30 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) { // 1 构建分页条件 Page<User> p = Page.of(pageQuery.getPageNo(), pageQuery.getPageSize()); // 2 根据指定参数进行升序排列 // 若获取的字符串不为null,且不为空则按照排序字段排序 if (StrUtil.isNotBlank(pageQuery.getSortBy())) { // 若排序字段不为空,则按照排序字段排序 if (pageQuery.getIsAsc()) p.addOrder(OrderItem.ascs(pageQuery.getSortBy())); else p.addOrder(OrderItem.descs(pageQuery.getSortBy())); } else { // 若排序字段为空,默认按照更新时间排序 p.addOrder(OrderItem.desc("update_time")); } // 调用UserService中的page方法进行分页查询 page(p);//等同于this.page(p); // 获取当前页的所有数据 List<User> users = p.getRecords(); // 若当前页无数据则返回空数据 if (CollUtil.isEmpty(users)) return PageVo.of(p.getTotal(), p.getPages(), Collections.emptyList()); // 当前页有数据则 else { // 将集合List<User>转换为集合List<UserVo> List<UserVo> userVos = BeanUtil.copyToList(users, UserVo.class); // 封装返回数据 return PageVo.of(p.getTotal(), p.getPages(), userVos); } } }
表现层
-
Step1: 在controller包下的UserController类中写入初始代码(采用构造器依赖注入IUserService接口对应的Bean)
-
方式一(推荐):
- 给需要进行依赖注入的属性添加
final
关键字(该关键字必须在初始时就给属性赋值,也就是说初始必须给其立马进行构造器依赖注入,否则报错) - 此时进行构造器依赖注入的注解可用
@RequiredArgsConstructor
或@AllArgsConstructor
(博主使用的是@RequiredArgsConstructor
)- 推荐使用
@RequiredArgsConstructor
:自动生成一个构造函数,该构造函数接受所有final
字段和@NonNull
注解字段。(也就是说该构造器的参数为所有包含final
关键字或@NonNull
注解的属性)
- 推荐使用
@RestController @RequestMapping("/user") // 构造器依赖注入 @RequiredArgsConstructor public class UserController { private final IUserService userService; /** * 分页查询 */ @GetMapping("/page") public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) { } }
- 给需要进行依赖注入的属性添加
-
方式二:
- 方式二并未给需要进行依赖注入的属性添加
final
关键字,这样也行,但是在实际开发项目中,controller类中可能需要很多的依赖注入,这就会导致忘记依赖注入,所以尽量不要用方式二
@RestController @RequestMapping("/user") // 构造器依赖注入 @AllArgsConstructor public class UserController { private IUserService userService; /** * 分页查询 */ @GetMapping("/page") public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) { } }
- 方式二并未给需要进行依赖注入的属性添加
-
-
Step2: 在UserController类中写入分页查询的代码
package at.guigu.controller; import at.guigu.domain.query.PageQuery; import at.guigu.domain.vo.PageVo; import at.guigu.domain.vo.UserVo; import at.guigu.service.IUserService; import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") // 构造器依赖注入 @AllArgsConstructor public class UserController { private IUserService userService; /** * 分页查询 */ @GetMapping("/page") public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) { return userService.queryUserByPage(pageQuery); } }
引导类运行测试
-
无请求参数
-
存在请求参数
-
注意
- 在上述实例中已实现了分页查询,若想要实现带条件的分页查询,只需要在query包下创建继承
PageQuery
类的子类(假设子类为UserPageQuery
),然后在该子类中进行条件查询即可(此处不在进行示例)
- 在上述实例中已实现了分页查询,若想要实现带条件的分页查询,只需要在query包下创建继承
案例优化
在业务层的实现类
UserServiceImpl
中,有些代码是固定不变的,所以我们可以对其进行优化,UserServiceImpl
类的初始完整代码如下:
package at.guigu.service.impl;
import at.guigu.domain.po.User;
import at.guigu.domain.query.PageQuery;
import at.guigu.domain.vo.PageVo;
import at.guigu.domain.vo.UserVo;
import at.guigu.mapper.UserMapper;
import at.guigu.service.IUserService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
/**
* <p>
* 用户表 服务实现类
* </p>
*
* @author cgrs572
* @since 2024-12-30
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
// 1 构建分页条件
Page<User> p = Page.of(pageQuery.getPageNo(), pageQuery.getPageSize());
// 2 根据指定参数进行升序排列
// 若获取的字符串不为null,且不为空则按照排序字段排序
if (StrUtil.isNotBlank(pageQuery.getSortBy())) {
// 若排序字段不为空,则按照排序字段排序
if (pageQuery.getIsAsc()) p.addOrder(OrderItem.ascs(pageQuery.getSortBy()));
else p.addOrder(OrderItem.descs(pageQuery.getSortBy()));
} else {
// 若排序字段为空,默认按照更新时间排序
p.addOrder(OrderItem.desc("update_time"));
}
// 调用UserService中的page方法进行分页查询
page(p);//等同于this.page(p);
// 获取当前页的所有数据
List<User> users = p.getRecords();
// 若当前页无数据则返回空数据
if (CollUtil.isEmpty(users)) return PageVo.of(p.getTotal(), p.getPages(), Collections.emptyList());
// 当前页有数据则
else {
// 将集合List<User>转换为集合List<UserVo>
List<UserVo> userVos = BeanUtil.copyToList(users, UserVo.class);
// 封装返回数据
return PageVo.of(p.getTotal(), p.getPages(), userVos);
}
}
}
优化一
优化构建分页条件、根据指定参数进行升序或降序排列。
UserServiceImpl
类相关的源代码如下:
// 1 构建分页条件
Page<User> p = Page.of(pageQuery.getPageNo(), pageQuery.getPageSize());
// 2 根据指定参数进行升序排列
// 若获取的字符串不为null,且不为空则按照排序字段排序
if (StrUtil.isNotBlank(pageQuery.getSortBy())) {
// 若排序字段不为空,则按照排序字段排序
if (pageQuery.getIsAsc()) p.addOrder(OrderItem.ascs(pageQuery.getSortBy()));
else p.addOrder(OrderItem.descs(pageQuery.getSortBy()));
} else {
// 若排序字段为空,默认按照更新时间排序
p.addOrder(OrderItem.desc("update_time"));
}
-
分页查询的请求参数实体
PageQuery
类代码如下:package at.guigu.domain.query; import lombok.Data; /** * 分页查询的请求参数实体 */ @Data public class PageQuery { // 当前页码 private Integer pageNo = 1; // 每页显示的数据量 private Integer pageSize = 5; // 定义需要排序的字段 private String sortBy; // 是否升序排列 private Boolean isAsc = false; }
在该
PageQuery
类中只有成员变量,并没有成员方法,此种情况属于贫血模型,不符合面向对象的思想,所以需要为其加上成员方法(即行为),变为充血模型(符合面向对象的思想)所以将 构建分页条件、根据指定参数进行升序或降序排列 的代码封装成一个成员方法写入
PageQuery
类中 -
Step1: 在
PageQuery
类中定义公共的成员方法toMpPage()
,完整PageQuery
类代码如下:package at.guigu.domain.query; import cn.hutool.core.util.StrUtil; 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 = 1; // 每页显示的数据量 private Integer pageSize = 5; // 定义需要排序的字段 private String sortBy; // 是否升序排列 private Boolean isAsc = false; /** * 单字段排序 * 若前端未传来参数则按照该方法的参数进行默认排序 * @param defaultSortBy * @param isAsc * @return Page<T> * @param <T> */ public <T> Page<T> toMpPage (String defaultSortBy, Boolean isAsc) { // 1 构建分页条件 Page<T> p = Page.of(pageNo, pageSize); //2 根据指定参数进行升序排列 // 若获取的字符串不为null,且不为空则按照排序字段排序 if (StrUtil.isNotBlank(sortBy)) { p.addOrder(this.isAsc ? OrderItem.ascs(sortBy) : OrderItem.descs(sortBy)); } else { // 若排序字段为空,默认按照传入参数排序 p.addOrder(isAsc ? OrderItem.asc(defaultSortBy) : OrderItem.desc(defaultSortBy)); } return p; } }
-
Step2: 业务层的实现类
UserServiceImpl
代码更改如下:package at.guigu.service.impl; import at.guigu.domain.po.User; import at.guigu.domain.query.PageQuery; import at.guigu.domain.vo.PageVo; import at.guigu.domain.vo.UserVo; import at.guigu.mapper.UserMapper; import at.guigu.service.IUserService; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; /** * <p> * 用户表 服务实现类 * </p> * * @author cgrs572 * @since 2024-12-30 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) { Page<User> p = pageQuery.toMpPage("update_time", false); // 调用UserService中的page方法进行分页查询 page(p);//等同于this.page(p); // 获取当前页的所有数据 List<User> users = p.getRecords(); // 若当前页无数据则返回空数据 if (CollUtil.isEmpty(users)) return PageVo.of(p.getTotal(), p.getPages(), Collections.emptyList()); // 当前页有数据则 else { // 将集合List<User>转换为集合List<UserVo> List<UserVo> userVos = BeanUtil.copyToList(users, UserVo.class); // 封装返回数据 return PageVo.of(p.getTotal(), p.getPages(), userVos); } } }
-
运行测试
-
无请求参数
-
有请求参数
-
优化二
优化将 集合
List<User>
转换为集合List<UserVo>
,UserServiceImpl
类相关的源代码如下:
// 获取当前页的所有数据
List<User> users = p.getRecords();
// 若当前页无数据则返回空数据
if (CollUtil.isEmpty(users)) return PageVo.of(p.getTotal(), p.getPages(), Collections.emptyList());
// 当前页有数据则
else {
// 将集合List<User>转换为集合List<UserVo>
List<UserVo> userVos = BeanUtil.copyToList(users, UserVo.class);
// 封装返回数据
return PageVo.of(p.getTotal(), p.getPages(), userVos);
}
-
分页查询的返回结果实体类
PageVo
,代码如下package at.guigu.domain.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class PageVo<T> { private Long total; private Long Pages; private List<T> list; }
-
Step1: 在
PageVo
类中定义构造器方法,完整PageVo
类代码如下:package at.guigu.domain.vo; import at.guigu.domain.po.User; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; 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; @Data @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class PageVo<T> { private Long total; private Long Pages; private List<T> list; public <P> PageVo(Page<P> page, Class<T> clazz){ // 总数据量 this.total = page.getTotal(); // 总页数 this.Pages = page.getPages(); // 当前页数据集合 List<P> users = page.getRecords(); // 若当前页无数据则返回空数据 if (CollUtil.isEmpty(users)){ list = Collections.emptyList(); return; } // 当前页有数据则 将集合List<P>转换为集合List<T> this.list = BeanUtil.copyToList(users, clazz); } }
-
Step2: 业务层的实现类
UserServiceImpl
代码更改如下:package at.guigu.service.impl; import at.guigu.domain.po.User; import at.guigu.domain.query.PageQuery; import at.guigu.domain.vo.PageVo; import at.guigu.domain.vo.UserVo; import at.guigu.mapper.UserMapper; import at.guigu.service.IUserService; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; /** * <p> * 用户表 服务实现类 * </p> * * @author cgrs572 * @since 2024-12-30 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) { Page<User> p = pageQuery.toMpPage("update_time", false); // 调用UserService中的page方法进行分页查询 page(p);//等同于this.page(p); // 封装返回数据 return new PageVo<>(p, UserVo.class); } }
-
运行测试
-
无请求参数
-
有请求参数
-
优化三
在前两个优化中,我们可以从结果中看到名字均完全暴露出来,若在实际开发中想要不完全显示(即张三四显示为张XX或张X四),则需要做以下步骤:
-
Step1: 在
PageVo
类中重载构造器方法,传入一个Function
参数,完整PageVo
类代码如下:package at.guigu.domain.vo; import at.guigu.domain.po.User; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; 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 @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class PageVo<T> { private Long total; private Long Pages; private List<T> list; public <P> PageVo(Page<P> page, Class<T> clazz){ // 总数据量 this.total = page.getTotal(); // 总页数 this.Pages = page.getPages(); // 当前页数据集合 List<P> users = page.getRecords(); // 若当前页无数据则返回空数据 if (CollUtil.isEmpty(users)){ list = Collections.emptyList(); return; } // 当前页有数据则 将集合List<P>转换为集合List<T> this.list = BeanUtil.copyToList(users, clazz); } public <P> PageVo(Page<P> page, Function<P, T> convertor){ // 总数据量 this.total = page.getTotal(); // 总页数 this.Pages = page.getPages(); // 当前页数据集合 List<P> users = page.getRecords(); // 若当前页无数据则返回空数据 if (CollUtil.isEmpty(users)){ list = Collections.emptyList(); return; } // 当前页有数据则 将集合List<P>转换为集合List<T> this.list = users.stream().map(convertor).collect(Collectors.toList()); } }
-
Step2: 业务层的实现类
UserServiceImpl
代码更改如下:package at.guigu.service.impl; import at.guigu.domain.po.User; import at.guigu.domain.query.PageQuery; import at.guigu.domain.vo.PageVo; import at.guigu.domain.vo.UserVo; import at.guigu.mapper.UserMapper; import at.guigu.service.IUserService; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; /** * <p> * 用户表 服务实现类 * </p> * * @author cgrs572 * @since 2024-12-30 */ // 优化三: @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) { Page<User> p = pageQuery.toMpPage("update_time", false); // 调用UserService中的page方法进行分页查询 page(p);//等同于this.page(p); // 封装返回数据 return new PageVo<>(p, user -> { UserVo vo = BeanUtil.copyProperties(user, UserVo.class); String username = vo.getUsername(); // 名字长度为1或2时,直接将第二个字符设置为 "*" if (username.length() <= 2) { vo.setUsername(username.substring(0) + "*"); //等同于vo.setUsername(username.charAt(0) + "*"); } else { // 名字长度大于2时,将中间部分替换为 "*" String masked = username.charAt(0) + "*".repeat(username.length() - 2) +username.charAt(username.length() - 1); vo.setUsername(masked); } return vo; }); } }
-
运行测试