概述

MyBatis-Plus(简称MP)是一个基于MyBatis框架的增强工具,它在MyBatis的基础上只做增强而不做改变,旨在简化开发、提高效率。MP提供了一系列的功能和特性,使得开发人员能够更加高效地使用MyBatis进行数据库操作

官网:MyBatis-Plus

快速入门

环境搭建:先利用Tlias系统进行测试,可以从中州养老项目中给出的资料获取项目代码

导入依赖

导入MyBatis-Plus的起步依赖,替换掉MyBatis的起步依赖

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>

接着,在引导类上添加注解,配置自动扫描Mapper

1
2
3
4
5
6
7
8
@MapperScan("com.itheima.mapper")   // 添加这段注解
@ServletComponentScan
@SpringBootApplication
public class TliasWebManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasWebManagementApplication.class, args);
}
}

定义Mapper

为了简化单表CRUD,Mybatis-Plus提供了一个基础的BaseMapper接口,其中已经实现了单表的CRUD:

Snipaste_2025-08-28_10-22-03

因此咱们自定义的Mapper只要实现了这个BaseMapper,就无需自己实现单表CRUD了。

修改DeptProjectMapper接口,让其继承BaseMapper,接口中的代码可以全部注释掉或者删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Mapper
public interface DeptMapper extends BaseMapper<Dept> {

/*
@Select("select id, name, create_time, update_time from dept order by update_time desc ")
List<Dept> findAll();

@Delete("delete from dept where id = #{id}")
void deleteById(Integer id);

@Insert("insert into dept(name, create_time, update_time) values (#{name}, #{createTime}, #{updateTime})")
void save(Dept dept);

Dept getById(Integer id);

@Update("update dept set name = #{name}, update_time = #{updateTime} where id = #{id}")
void update(Dept dept);
*/
}

同时可以把对应的xml映射文件改个名字或删除(彻底失效)

由于DeptMapper中进行了修改,所以还需要将对应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
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;


@Override
public List<Dept> findAll() {
return deptMapper.selectList(null);
}

@Override
public void deleteById(Integer id) {
deptMapper.deleteById(id);
}

@Override
public void save(Dept dept) {
// 补全基础属性
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());

// 调用Mapper层方法保存数据
deptMapper.insert(dept);
}

@Override
public Dept getById(Integer id) {
Dept dept = deptMapper.selectById(id);
return dept;
}

@Override
public void update(Dept dept) {
// 补全基础属性
dept.setUpdateTime(LocalDateTime.now());
deptMapper.updateById(dept);
}
}

最后,修改Dept.java这个实体类,设置主键增长策略为自增:

1
2
3
4
5
6
7
8
9
10
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String name;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

常见注解(实体类映射数据表)

在上面的案例中,对于Mapper层来说,虽然没有编写任何SQL代码,但是MP依旧可以知道是对哪张表进行操作,主要的原因如下:
DeptMapper在继承BaseMapper时,指定了一个泛型

1
2
3
4
@Mapper
public interface DeptMapper extends BaseMapper<Dept> {

}

泛型中的Dept就是与数据库表对应的POJO

MP就是根据实体类的信息,通过反射来推断出表的信息,从而生成SQL的。规则如下:

  • MP会把实体类的类名(驼峰转下划线)作为表名
  • MP会把名为id的字段作为主键
  • MP会把实体类的所有变量名(驼峰转下划线)作为表的字段名,并根据变量类型推断字段类型

但有些情况下,实体类的类名与表名不完全一致,又或者实体类中的属性名和数据表中的字段名无法完全对应,因此MP提供了一些注解便于声明表信息,常用的有下面几个注解:

  • @TableName:用来指定表名
  • @TableField:用来指定表中的普通字段信息
  • @TableId:用来指定表中的主键字段信息

@TableName

  • 描述:表名注解,标识实体类对应的表
  • 使用位置:实体类
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")
public class User {
private Long id;
private String name;
private Boolean isMarried;
private Integer order;
private String address;
}

@TableField

  • 普通字段注解
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")
public class User {
private Long id;
@TableField("username")
private String name;
private Boolean isMarried;
@TableField("`order`")
private Integer order;
@TableField(exist=false)
private String address;
}

一般情况下是不需要为成员变量添加@TableField注解,一些特殊情况除外:

  • 成员变量名与数据库字段名不一致
  • 成员变量与数据库一致,但是与数据库的关键字冲突。使用@TableField注解结合``指定字段名称
  • 成员变量不是数据库中的字段,则需要将exist属性设置为false,明确告知MP该字段在数据表中不存在

@TableId

  • 描述:主键注解,标识实体类中的主键字段和主键生成类型
  • 使用位置:实体类的主键字段
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;

private String name;

private Boolean isMarried;

private Integer order;

private String address;
}
  • @TableId注解支持两个属性:

value:指定数据库表中的主键字段名称

IdType:指定主键生成类型,支持的类型有:

  • **IdType.AUTO**:使用数据库自增 ID 作为主键。
  • IdType.NONE:无特定生成策略,如果全局配置中有 IdType 相关的配置,则会跟随全局配置。
  • IdType.INPUT:在插入数据前,由用户自行设置主键值。
  • **IdType.ASSIGN_ID**:自动分配 ID,适用于 LongIntegerString 类型的主键。

默认使用雪花算法通过 IdentifierGeneratornextId 实现,这是默认的ID策略

  • IdType.ASSIGN_UUID:自动分配 UUID,适用于 String 类型的主键。默认实现为 IdentifierGeneratornextUUID 方法。

常见配置

Mybatis-Plus也支持基于yml文件的自定义配置,详见官方文档:使用配置 | MyBatis-Plus

常见的配置:

  • 全局id类型
1
2
3
4
5
mybatis-plus:
global-config:
db-config:
id-type: auto # 全局id类型为自增长
update-strategy: not_null # 更新策略:只更新非空字段

Mybatis-Plus也支持手写SQL的,而mapper层的xml映射文件的读取位置可以自己配置:

  • type-aliases-package:指定 MyBatis 别名包扫描路径,用于给包中的类注册别名。注册后,在 Mapper 对应的 XML 文件中可以直接使用类名,无需使用全限定类名
  • mapper-locations:指定 MyBatis 的 Mapper 层 XML 映射文件的位置,默认值["classpath*:/mapper/**/*.xml"]
1
2
3
4
5
6
7
8
9
10
11
mybatis-plus:
type-aliases-package: com.itheima.pojo
mapper-locations: classpath:mapper/**/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

global-config:
db-config:
id-type: auto # 全局id类型为自增长
update-strategy: not_null # 更新策略:只更新非空字段

mapper-locations也可以配置成classpath*:mapper/**.xml,也就是说只要把mapper.xml文件放置在mapper目录下就一定会被加载。

通过以上的配置信息,可以将DeptMapper.xml中的实体类resultType属性的包名省略:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.DeptMapper">
<select id="getById" resultType="Dept"> <!-- 原本resultType="com.itheima.pojo.Dept" -->
select id, name, create_time, update_time from dept where id = #{id}
</select>
</mapper>

核心功能

刚才的案例中都是以id为条件的简单CRUD,一些复杂条件的SQL语句就要用到一些更高级的功能了,Mybatis-Plus中的核心功能,包括两部分内容:

  • 条件构建器
  • IService接口

条件构建器

除了查询以外,修改、删除的SQL语句都需要指定where条件。因此BaseMapper中不仅仅提供了接收id作为where条件的方法,还提供了一组可以接收更加复杂的where条件的方法

c10c1fd8-0ea0-4cae-aa9b-647e3d6a0cec

参数中的Wrapper就是条件构建的抽象类,其下有很多默认实现,继承关系如图:

c09dde99-df24-4ec2-bc48-bf879dc70a29

Wrapper的子类AbstractWrapper提供了where中包含的所有类型条件的构建方法:

c09dde99-df24-4ec2-bc48-bf879dc70a20

而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:

c09dde99-df24-4ec2-bc48-bf879dc70a21

而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:

c09dde99-df24-4ec2-bc48-bf879dc70a22

QueryWrapper

无论是查询、修改、删除,都可以使用QueryWrapper来构建条件

示例1:

  • 需求:查询姓名中包含“李”且薪资大于等于5000的员工的 id, name, phone, salary字段

  • SQL语句:select id, name, phone, salary from emp where name like '%李%' and salary >= 5000;

  • 实现代码如下:

    1. 首先让EmpMapper继承自BaseMapper:

      1
      2
      3
      4
      @Mapper
      public interface EmpMapper extends BaseMapper<Emp> {

      }
    2. 其次,在测试类中注入EmpMapper,并创建条件构建器,最后使用BaseMapper的selectList方法,并传入构建器:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      @SpringBootTest
      public class TestQueryWrapper {

      @Autowired
      private EmpMapper empMapper;

      @Test
      public void testQueryWrapper() {
      // 查询姓名中包含“李”且薪资大于等于5000的员工的 id, name, phone, salary字段
      QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
      queryWrapper.like("name", "李")
      .ge("salary", 5000)
      .select("id", "name", "phone", "salary");

      List<Emp> emps = empMapper.selectList(queryWrapper);
      emps.forEach(System.out::println);
      }
      }

示例2:

  • 需求:更新名为”李忠”的员工的薪水为9000

  • 实现代码如下:

    1. 第一步和示例1一样,这里省略

    2. 依旧是注入EmpMapper,然后创建条件构建器,并使用BaseMapper的update方法,传入构建器:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Autowired
      private EmpMapper empMapper;

      @Test
      public void testUpdateByQueryWrapper() {
      // 更新名为"李忠"的员工的薪水为9000
      Emp emp = new Emp();
      emp.setSalary(9000);
      QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
      queryWrapper.eq("name", "李忠");
      empMapper.update(emp, queryWrapper);
      }

UpdateWrapper

示例:

  • 需求:更新id为5, 6, 7的员工的薪水,加2000

  • SQL语句:update emp set salary = salary + 2000 where id in (5, 6, 7);

  • 实现代码如下:

    1. EmpMapper继承BaseMapper,测试类注入EmpMapper,这两步还是和QueryWrapper中的示例一样

    2. 具体测试方法如下:

      1
      2
      3
      4
      5
      6
      7
      8
      @Test
      public void testUpdateWrapper() {
      // 更新id为5, 6, 7的员工的薪水,加2000
      UpdateWrapper<Emp> updateWrapper = new UpdateWrapper<>();
      updateWrapper.in("id", 5, 6, 7)
      .setSql("salary = salary + 2000");
      empMapper.update(updateWrapper);
      }

setSql中的参数就是SQL语句里set后面跟随的更新内容

LambdaQueryWrapper

无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,这在编程规范中显然是不推荐

那怎么样才能不写字段名,又能知道字段名呢?

其中一种办法是基于变量的**getter**方法结合反射技术来实现,因此咱们只要将条件对应的字段的getter方法传递给Mybatis-Plus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用Lambda表达式。 因此Mybatis-Plus又提供了一套基于Lambda的Wrapper,包含两个类:

  • LambdaQueryWrapper
  • LambdaUpdateWrapper

分别对应QueryWrapper和UpdateWrapper

其使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testLambdaQueryWrapper() {
// 查询姓名中包含“李”且薪资大于等于5000的员工的 id, name, phone, salary字段
LambdaQueryWrapper<Emp> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(Emp::getName, "李")
.gt(Emp::getSalary, 5000)
.select(Emp::getId, Emp::getName, Emp::getPhone, Emp::getSalary);

List<Emp> emps = empMapper.selectList(queryWrapper);
System.out.println(emps);
}

@Test
public void testLambdaUpdateWrapper() {
// 更新id为5, 6, 7的员工的薪水,加2000
LambdaUpdateWrapper<Emp> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.in(Emp::getId, 5, 6, 7)
.setSql("salary = salary + 2000");
empMapper.update(updateWrapper);
}

IService

MP不仅提供了Map层接口BaseMapper,还提供了通用的Service层接口以及默认实现,并在其中封装了一些常用的service模板方法。通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

  • save:新增
  • remove:删除
  • update:更新
  • get:查询单个结果
  • list:查询集合结果
  • count:计数
  • page:分页查询

基本增删改查

新增

e0e47195-c06e-4594-916e-d9e2932c324a

  • **save**:新增单个元素
  • **saveBatch**:批量新增
  • saveOrUpdate是根据id判断,如果数据存在就更新,不存在则新增
  • saveOrUpdateBatch是批量的新增或修改

删除:

c9e1a381-f921-414a-a953-9c440b8062f2

  • **removeById**:根据id删除
  • removeByIds:根据id批量删除
  • removeByMap:根据Map中的键值对条件删除
  • remove(Wrapper<T>):根据Wrapper条件删除

修改:

17686e2d-589a-40f5-8222-9c49e2269eef

  • **updateById**:根据id修改
  • update(Wrapper<T>):根据UpdateWrapper修改,Wrapper中包含setwhere部分
  • update(T,Wrapper<T>):按照T内的数据修改与Wrapper匹配到的数据
  • updateBatchById:根据id批量修改

查询单个数据:

c98ec5fb-5ef1-4ba1-a2a7-d903119a75fd

  • getById:根据id查询1条数据
  • getOne(Wrapper<T>):根据Wrapper查询1条数据
  • getBaseMapper:获取Service内的BaseMapper实现,某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper

查询多条数据:

b3b76d6b-a34c-4bee-90d9-b03b24be0716

  • listByIds:根据id批量查询
  • list(Wrapper<T>):根据Wrapper条件查询多条数据
  • list():查询所有

统计数据:

c8f0e61d-d4d7-4e82-b34e-0c717f45ac65

  • count():统计所有数量
  • count(Wrapper<T>):统计符合Wrapper条件的数据数量

基本用法与快速入门

d726cf07-d2a7-4fc8-b2aa-cc18bcc352eb

由于Service中经常需要定义与业务有关的自定义方法,因此咱们不能直接使用IService,而是自定义Service接口,然后继承IService以拓展方法。同时,让自定义的Service实现类继承ServiceImpl,这样就不用自己实现IService中的接口了

需求:基于Mybatis-Plus的 Iservice 接口,实现Tlias智能学习辅助系统部门管理页面的所有功能:

  • 新增部门
  • 根据id查询部门
  • 根据id更新部门
  • 根据id删除部门

首先,找到DeptService接口,让它继承IService接口,并在泛型上指定实体类的类型:

1
2
3
public interface DeptService extends IService<Dept> {

}

改完之后,原来书写的方法可以全部删除

接着,找到DeptService接口的实现类:DeptServiceImpl,让它继承自ServiceImpl类,在泛型的位置指定Mapper层接口和实体类:

1
2
3
4
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {

}

最后,修改DeptController中报错的代码,改一下方法调用就可以正常运行了

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
@Slf4j
@RequestMapping("/depts")
@RestController
public class DeptController {
@Autowired
private DeptService deptService;

@GetMapping
public Result findAll() {
log.info("查询所有部门数据");

List<Dept> depts = deptService.list();

return Result.success(depts);
}

// 删除部门
@DeleteMapping
public Result deleteById(Integer id) {
log.info("根据id删除部门,删除的部门id:{}", id);
deptService.removeById(id);
return Result.success();
}

// 新增部门
@PostMapping
public Result save(@RequestBody Dept dept) {
log.info("新增部门,要新增的部门信息:{}", dept);
deptService.save(dept);
return Result.success();
}

// 根据ID查询部门信息
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
log.info("根据id查询部门,要查询的id:{}", id);

Dept dept = deptService.getById(id);

return Result.success(dept);
}

// 修改部门
@PutMapping
public Result update(@RequestBody Dept dept) {
log.info("修改部门数据,修改后的部门数据:{}", dept);
deptService.updateById(dept);
return Result.success();
}
}

实战

准备工作

Tlias员工管理部分,由MyBatis升级为MyBatis-Plus

由于员工管理中存在分页条件查询的功能,所以还需要使用到MP中的分页功能(PaginationInnerInterceptor)

v3.5.9 起,PaginationInnerInterceptor 已分离出来。如需使用,则需单独引入 mybatis-plus-jsqlparser 依赖

此时,由于项目中引入的MP依赖为3.5.11版本:

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>

所以还需要单独引入mybatis-plus-jsqlparser

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>3.5.11</version>
</dependency>

接下来,在项目中新建一个配置类:com.itheima.config.MybatisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.itheima.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化Mybatis-Plus核心插件
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 添加分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(1000L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);

return mybatisPlusInterceptor;
}
}

最后一步,就是业务层的继承和实现关系:

  1. 首先先将EmpMapper继承BaseMapper:
1
2
3
4
@Mapper
public interface StudentMapper {

}
  1. 其次,再对Service的Emp进行修改,先是EmpService接口,需要继承IService接口:
1
2
3
public interface EmpService extends IService<Emp> {

}
  1. 然后对其实现类增加ServiceImpl的继承:
1
2
3
4
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {

}

分页查询修改

在控制层中编写分页查询的接口:

1
2
3
4
5
6
7
8
9
10
@GetMapping
public Result page(EmpQueryParam param) {
log.info("员工列表查询条件:{}", param);

// 调用Service层查询分页数据
PageResult<Emp> pageResult = empService.getPageResult(param);

// 返回结果
return Result.success(pageResult);
}

业务层代码:

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
@Autowired
private DeptService deptService;

@Override
public PageResult<Emp> getPageResult(EmpQueryParam param) {
// 1. 创建分页参数对象
Page<Emp> pageParam = Page.of(param.getPage(), param.getPageSize());

// 2. 设置排序条件
pageParam.addOrder(OrderItem.desc("update_time"));

// 3. 执行分页查询,获取结果
Page<Emp> pageResult = page(pageParam);

// 获取员工集合
List<Emp> emps = page.getRecords();

// 获取所有员工的部门id
List<Integer> deptIds = emps.stream().map(Emp::getDeptId).collect(Collectors.toList());
if (!deptIds.isEmpty()) {
// 批量查询这些部门的信息
List<Dept> depts = deptService.listByIds(deptIds);
// 将部门名称封装到员工对象中
emps.forEach(emp -> {
depts.forEach(dept -> {
if (emp.getDeptId() != null && emp.getDeptId().equals(dept.getId())) {
emp.setDeptName(dept.getName());
}
});
});
}

// 封装结果并返回
return new PageResult<>(page.getTotal(), emps);
}

原本的分页查询中,SQL语句是多表查询,但是MP不支持多表查询,所以可以拆分成两次单表查询

第一次查询emp表,将所有员工数据中的dept_id保存到集合中

第二次查询dept表,根据dept_id获取对应的部门名称,将部门名称再封装到Emp对象中

分页条件查询

page方法不仅可以传入分页参数,还可以传入构建器wrapper

1
page(E page, Wrapper<T> queryWrapper)

所以,在原有代码中,加入构建器并作为参数传入page就能实现分页条件查询:

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
@Override
public PageResult<Emp> getPageResult(EmpQueryParam param) {
// 1. 创建分页参数对象
Page<Emp> pageParam = Page.of(param.getPage(), param.getPageSize());

// 2.1 设置排序条件
pageParam.addOrder(OrderItem.desc("update_time"));

// 2.2 添加查询条件
LambdaQueryWrapper<Emp> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper
.like(param.getName() != null && !param.getName().isEmpty(), Emp::getName, param.getName())
.eq(param.getGender() != null, Emp::getGender, param.getGender())
.between(param.getBegin() != null && param.getEnd() != null, Emp::getEntryDate, param.getBegin(), param.getEnd());

// 3. 执行分页查询,获取结果
Page<Emp> pageResult = page(pageParam, queryWrapper);

// 获取员工集合
List<Emp> emps = pageResult.getRecords();

// 获取所有员工的部门id
List<Integer> deptIds = emps.stream().map(Emp::getDeptId).collect(Collectors.toList());

if (!deptIds.isEmpty()) {
// 批量查询这些部门的信息
List<Dept> depts = deptService.listByIds(deptIds);

// 将部门名称封装到员工对象中
emps.forEach(emp -> {
depts.forEach(dept -> {
if (emp.getDeptId() != null && emp.getDeptId().equals(dept.getId())) {
emp.setDeptName(dept.getName());
}
});
});
}

// 封装结果并返回
return new PageResult<>(pageResult.getTotal(), emps);
}