Mybatis-Plus
Mybatis-Plus
1.概述
Mybatis-Plus(简称MP)是一个基于Mybatis框架的增强工具,它在Mybatis的基础上只做增强而不做改变,旨在简化开发、提高效率。Mybatis-Plus提供了一系列的功能和特性,使得开发人员能够更加高效地使用Mybatis进行数据库操作。
官网地址:https://mybatis.plus/
2.快速入门
2.1环境准备
咱们基于Web阶段学习过的Tlias智能学习辅助系统中的部门管理和员工管理页面来完成MyBatisPlus的学习。
导入资料中提供的tlias-web-management.zip初始项目,使用idea打开即可,项目的结构如下:

打开项目后,执行sql目录下的tlias-mp.sql创建一个新的数据库tlias_mp,里面包含了部门管理、员工管理、班级管理、学员管理相关的表结构。
在application.yml中修改jdbc参数为你自己的数据库参数
spring:
datasource:
# 数据库连接四要素
url: jdbc:mysql://localhost:3306/tlias_mp
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 1234
# 配置Spring事务管理的debug级别日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
# 配置MyBatis的日志输出
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true # 开启驼峰命名环境准备好之后,可以运行启动类,然后打开资料中提供的前端项目来验证部门管理页面是否能正常操作数据库,其他页面只提供了基础的Controller类,没有Service层业务逻辑,所以都是没有数据的
2.2需求描述
基于Mybatis-Plus,实现Tlias智能学习辅助系统部门管理页面的所有功能:
①查询所有部门
②新增部门
③根据id查询部门
④根据id更新部门
⑤根据id删除部门
2.3导入依赖
导入Mybatis-Plus的起步依赖,替换掉MyBatis的起步依赖
<!--Mybatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>接着,在引导类上添加注解,配置自动扫描Mapper:
@MapperScan("com.itheima.mapper")
@ServletComponentScan
@SpringBootApplication
public class TliasMangementSystemApplication {
public static void main(String[] args) {
SpringApplication.run(TliasMangementSystemApplication.class, args);
}
}2.4定义mapper
为了简化单表CRUD,Mybatis-Plus提供了一个基础的BaseMapper接口,其中已经实现了单表的CRUD:

因此咱们自定义的Mapper只要实现了这个BaseMapper,就无需自己实现单表CRUD了。
修改DeptProjectMapper接口,让其继承BaseMapper,接口中的代码可以全部注释掉或者删除:
package com.itheima.mapper;
import com.baomidou.Mybatis-Plus.core.mapper.BaseMapper;
import com.itheima.pojo.Dept;
@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);
@Select("select id, name, create_time, update_time from dept where id = #{id}")
Dept getById(Integer id);
void update(Dept dept);
*/
}同时可以把原有的基础代码给注释掉或者删除掉,把对应的xml映射文件改个名字或删除(彻底失效)

修改Service层实现类:DeptServiceImpl,内容如下:
package com.itheima.service.impl;
import com.itheima.mapper.DeptMapper;
import com.itheima.pojo.Dept;
import com.itheima.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@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);
}
}以上的所有操作数据库的方法,都是继承了BaseMapper之后提供好的方法,可以正常操作数据库
最后,修改Dept.java,设置主键增长策略为自增:
package com.itheima.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}修改完成后,启动后端程序和nginx程序,打开前端页面,在部门管理页面执行增删改查,是不是都是可以正常使用呀,如果是的话,那就恭喜你啦,成功入门Mybatis-Plus啦!
2.5常见注解
在刚刚的入门案例中,咱们仅仅引入了依赖,继承了BaseMapper就能使用Mybatis-Plus,非常简单。但是问题来了: Mybatis-Plus如何知道咱们要查询的是哪张表?表中有哪些字段呢?
大家回忆一下,DeptMapper在继承BaseMapper的时候是不是指定了一个泛型呀:
public interface DeptMapper extends BaseMapper<Dept> {
}泛型中的Dept就是与数据库表对应的pojo
Mybatis-Plus就是根据pojo实体的信息通过反射来推断出表的信息,从而生成SQL的。规则如下:
- Mybatis-Plus会把pojo实体的类名驼峰转下划线作为表名
- Mybatis-Plus会把名为id的字段作为主键
- Mybatis-Plus会把pojo实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
但有些情况下,实体类的类名与表名不完全一致,或者实体类中的属性名和数据库表中的字段名无法完全对应,因此Mybatis-Plus提供了一些注解便于咱们声明表信息,常用的有下面几个注解:
@TableName: 用来指定表名
@TableField: 用来指定表中的普通字段信息
@TableId: 用来指定表中的主键字段信息
@TableName
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
- 示例:
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
private Long id;
private String name;
private Boolean isMarried;
private Integer order;
private String address;
}@TableField
- 普通字段注解
- 示例
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
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,明确告知MyBatisPlus该字段在数据库表中不存在。
@TableId
- 描述:主键注解,标识实体类中的主键字段和主键生成类型
- 使用位置:实体类的主键字段
- 示例
@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,适用于Long、Integer、String类型的主键。
默认使用雪花算法通过 IdentifierGenerator 的 nextId 实现,这是默认的ID策略。
IdType.ASSIGN_UUID:自动分配UUID,适用于String类型的主键。默认实现为IdentifierGenerator的nextUUID方法。
2.6常见配置
Mybatis-Plus也支持基于yml文件的自定义配置,详见官方文档:https://www.baomidou.com/reference/
大多数的配置都有默认值,因此咱们都无需配置。但还有一些是没有默认值的,接下来,一起来看一些常见的配置:
- 全局id类型
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"]
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文件中的sql片段,同时将resultType属性中的包名省略:
<?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="com.itheima.pojo.Dept">
select id, name, create_time, update_time from dept where id = #{id}
</select>
</mapper>接着,放开DeptMapper.java中的getById方法:
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);
@Update("update dept set name = #{name}, update_time = #{updateTime} where id = #{id}")
void update(Dept dept);
*/
Dept getById(Integer id);
}最后,修改DeptServiceImpl中的getById方法:
@Override
public Dept getById(Integer id) {
// Dept dept = deptMapper.selectById(id);
Dept dept = deptMapper.getById(id);
return dept;
}重启后端程序,测试点击修改按钮根据id查询功能,依然正常可用,说明此时咱们放在mapper目录下的xml映射文件生效了,同时配置中的别名包扫描路径也生效了。
3.核心功能
刚才的案例中都是以id为条件的简单CRUD,一些复杂条件的SQL语句就要用到一些更高级的功能了,所以,接下来咱们开始学习Mybatis-Plus中的核心功能,包括两部分内容:
- 条件构建器
- IService接口
3.1条件构建器
除了查询以外,修改、删除的SQL语句都需要指定where条件。因此BaseMapper中不仅仅提供了接收id作为where条件的方法,还提供了一组可以接收更加复杂的where条件的方法。

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

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

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

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

接下来,咱们就来看看如何利用Wrapper实现复杂查询。
3.1.1QueryWrapper
无论是查询、修改、删除,都可以使用QueryWrapper来构建条件。接下来看一些例子: 查询:查询姓名中包含“李”且薪资大于等于5000的员工的 id, name, phone, salary字段。代码如下:
select id, name, phone, salary from emp where name like '%李%' and salary >= 5000;首先让EmpMapper继承自BaseMapper:
@Mapper
public interface EmpMapper extends BaseMapper<Emp> {
}为了方便测试,咱们使用单元测试学习QueryWrapper的使用:
package com.itheima;
import com.baomidou.Mybatis-Plus.core.conditions.query.QueryWrapper;
import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@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);
}
}更新:更新名为"李忠"的员工的薪水为9000,代码如下:
@Test
public void testUpdateByQueryWrapper() {
// 更新名为"李忠"的员工的薪水为9000
Emp emp = new Emp();
emp.setSalary(9000);
QueryWrapper<Emp> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "李忠");
empMapper.update(emp, queryWrapper);
}3.1.2UpdateWrapper
更新*:更新id为5, 6, 7的员工的薪水,加2000*,代码如下:
update emp set salary = salary + 2000 where id in (5, 6, 7);
@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);
}3.1.3LambdaQueryWrapper
无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,这在编程规范中显然是不推荐的。 那怎么样才能不写字段名,又能知道字段名呢?
其中一种办法是基于变量的getter方法结合反射技术来实现,因此咱们只要将条件对应的字段的getter方法传递给Mybatis-Plus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用和Lambda表达式。 因此Mybatis-Plus又提供了一套基于Lambda的Wrapper,包含两个类:
- LambdaQueryWrapper
- LambdaUpdateWrapper
分别对应QueryWrapper和UpdateWrapper
其使用方式如下:
@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);
}3.2IService接口
Mybatis-Plus不仅提供了Map层接口BaseMapper,还提供了通用的Service层接口及默认实现,并在其中封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:
save:新增remove:删除update:更新get:查询单个结果list:查询集合结果count:计数page:分页查询
3.2.1基本的增删改查
咱们先来看下基本的CRUD接口,新增:

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

removeById:根据id删除removeByIds:根据id批量删除removeByMap:根据Map中的键值对条件删除remove(Wrapper<T>):根据Wrapper条件删除~~removeBatchByIds~~:暂不支持
修改:

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

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

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

count():统计所有数量count(Wrapper<T>):统计符合Wrapper条件的数据数量
3.2.2基本用法与快速入门

由于Service中经常需要定义与业务有关的自定义方法,因此咱们不能直接使用IService,而是自定义Service接口,然后继承IService以拓展方法。同时,让自定义的Service实现类继承ServiceImpl,这样就不用自己实现IService中的接口了。
怎么样?听上去是不是很厉害?那接下来咱们就通过一个入门程序来感受一下是不是如此吧。
需求:基于Mybatis-Plus的 Iservice 接口,实现Tlias智能学习辅助系统部门管理页面的所有功能:
- 新增部门
- 根据id查询部门
- 根据id更新部门
- 根据id删除部门
首先,找到DeptService接口,让它继承IService接口,并在泛型上指定实体类的类型:
public interface DeptService extends IService<Dept> {
}改完之后,里面原来书写的方法都可以全部删除啦。
接着,找到DeptService接口的实现类:DeptServiceImpl,让它继承自ServiceImpl类,在泛型的位置指定Mapper层接口和实体类:
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {
}最后,修改DeptController中报错的代码,改一下方法调用就可以啦。
@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();
}
}这样,就搞定了哦,接下来,重启程序,打开页面测试一下部门管理的增删改查是不是都还完全正确吧~
到这里,咱们就搞定了Mybatis-Plus的基本使用啦,接下来,咱们一起通过员工管理页面来实战一下,在实战中也会引入一些新的技巧哦。
4.员工管理页面Mybatis-Plus实战
4.1分页查询
员工管理列表页是条件分页查询,先来完成基础的分页查询功能,Mybatis-Plus提供了一个分页插件(官网链接:https://baomidou.com/plugins/pagination/),只有引入这个分页插件,才能支持分页查询。
官网中有一段明确的说明:
于 v3.5.9 起,PaginationInnerInterceptor 已分离出来。如需使用,则需单独引入 mybatis-plus-jsqlparser 依赖
咱们当前引入的MyBatisPlus版本是:3.5.11。因此,要想使用分页功能,还需要再引入一个依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>3.5.11</version>
</dependency>接下来,在项目中新建一个配置类:com.itheima.config.MybatisConfig
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;
}
}引入分页插件后,就可以使用分页的API了:

修改EmpController层代码,调用EmpService层代码完成分页,这一步先不考虑条件查询哦:
EmpController代码如下:
@GetMapping
public Result page(EmpQueryParam param) {
log.info("员工列表查询条件:{}", param);
// 调用Service层查询分页数据
PageResult<Emp> pageResult = empService.getPageResult(param);
// 返回结果
return Result.success(pageResult);
}EmpService代码如下:
public interface EmpService extends IService<Emp> {
/**
* 分页查询
* @param param 查询条件
* @return 分页结果
*/
PageResult<Emp> getPageResult(EmpQueryParam param);
}对啦,友情提醒,千万不要忘记EmpService和EmpServiceImpl上要做的操作呢!
还有,要记得,Service层的分页查询要根据最后修改时间字段倒序排序哦;
EmpServiceImpl代码如下:
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
/**
* 分页查询
*
* @param param 查询条件
* @return 分页结果
*/
@Override
public PageResult<Emp> getPageResult(EmpQueryParam param) {
Page<Emp> page = Page.of(param.getPage(), param.getPageSize());
// 根据最后修改时间倒序排序
page.addOrder(OrderItem.desc("update_time"));
// 调用page方法完成分页查询
page = page(page);
// 封装结果并返回
return new PageResult<>(page.getTotal(), page.getRecords());
}
}接下来,解决所属部门列没有数据的问题
部门名称需要关联部门表查询,而Mybatis-Plus是基于单表操作的,怎么办呢?
将多表操作转换成多次单表操作,查询出当前页面所有员工的部门数据,在内存中将部门名称关联到员工对象中。
先将Emp类中的部门名称字段取消注释,同时添加注解标注表中不存在该字段:
@Data
public class Emp {
private Integer id; //ID,主键
private String username; //用户名
private String password; //密码
private String name; //姓名
private Integer gender; //性别, 1:男, 2:女
private String phone; //手机号
private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师
private Integer salary; //薪资
private String image; //头像
private LocalDate entryDate; //入职日期
private Integer deptId; //关联的部门ID
private LocalDateTime createTime; //创建时间
private LocalDateTime updateTime; //修改时间
//封装部门名称数
@TableField(exist = false)
private String deptName; //部门名称
// 封装工作经历列表
// private List<EmpExpr> exprList;
}EmpServiceImpl中修改后的代码如下:
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
@Autowired
private DeptService deptService;
/**
* 分页查询
*
* @param param 查询条件
* @return 分页结果
*/
@Override
public PageResult<Emp> getPageResult(EmpQueryParam param) {
Page<Emp> page = Page.of(param.getPage(), param.getPageSize());
// 根据最后修改时间倒序排序
page.addOrder(OrderItem.desc("update_time"));
// 调用page方法完成分页查询
page = page(page);
// 获取员工集合
List<Emp> emps = page.getRecords();
// 获取所有员工的部门id
List<Long> deptIds = emps.stream().map(emp -> 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);
}
}注意第28行最好先判断一下获取到的部门id列表不为空,再查询对应的部门名称,所以要加一个判断,排除掉查询到的部门列表为空的情况,否则程序运行到这里的第33行会报错呢。
4.2条件分页查询
怎么样,在Mybatis-Plus中分页查询是不是很简单?那接下来,再来感受一下条件分页查询的强大之处吧。
IService接口中的page方法除了能接收一个Page对象作为参数外,还支持传入一个Wrapper对象,同时封装查询条件:
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
return this.getBaseMapper().selectPage(page, queryWrapper);
}接下来,就一起来改造分页查询的方法,添加查询条件吧
/**
* 分页查询
*
* @param param 查询条件
* @return 分页结果
*/
@Override
public PageResult<Emp> getPageResult(EmpQueryParam param) {
Page<Emp> page = Page.of(param.getPage(), param.getPageSize());
// 根据最后修改时间倒序排序
page.addOrder(OrderItem.desc("update_time"));
// 构建查询条件
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());
// 调用page方法完成分页查询
page = page(page, queryWrapper);
// 获取员工集合
List<Emp> emps = page.getRecords();
// 获取所有员工的部门id
List<Long> deptIds = emps.stream().map(emp -> 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);
}到这里,就实现了员工管理的分页查询,不过咱们还可以继续改造它,上面代码片段中绿色背景的代码还要自己new一个Wrapper对象,并将其作为参数传入到page方法内部,还是有点麻烦,这里可以借助于IService中的另外一个方法:lambdaQuery()进一步简化,最终的代码如下:
/**
* 分页查询
*
* @param param 查询条件
* @return 分页结果
*/
@Override
public PageResult<Emp> getPageResult(EmpQueryParam param) {
Page<Emp> page = Page.of(param.getPage(), param.getPageSize());
// 根据最后修改时间倒序排序
page.addOrder(OrderItem.desc("update_time"));
// 使用lambdaQuery方法构建查询条件
page = lambdaQuery().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())
.page(page);
// 获取员工集合
List<Emp> emps = page.getRecords();
// 获取所有员工的部门id
List<Long> deptIds = emps.stream().map(emp -> 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);
}4.3新增员工
由于初始项目中去除了阿里云OSS相关的代码,本次新增员工不考虑头像上传,只处理员工基本信息和工作经历信息。
先将Emp中的工作经历列表字段的注释取消,并标记字段不在数据库中。
@Data
public class Emp {
private Integer id; //ID,主键
private String username; //用户名
private String password; //密码
private String name; //姓名
private Integer gender; //性别, 1:男, 2:女
private String phone; //手机号
private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师
private Integer salary; //薪资
private String image; //头像
private LocalDate entryDate; //入职日期
private Integer deptId; //关联的部门ID
private LocalDateTime createTime; //创建时间
private LocalDateTime updateTime; //修改时间
// 封装部门名称数
@TableField(exist = false)
private String deptName; //部门名称
// 封装工作经历列表
@TableField(exist = false)
private List<EmpExpr> exprList;
}在EmpController中调用Service层方法,将数据传给Service层处理,代码如下:
@PostMapping
public Result save(@RequestBody Emp emp) {
log.info("新增员工,员工信息:{}", emp);
// 调用service层方法保存员工信息和员工的工作经历信息
empService.saveEmp(emp);
return Result.success();
}在EmpService中添加Controller层调用的方法:
public interface EmpService extends IService<Emp> {
/**
* 分页查询员工信息
* @param param 分页参数和查询条件
* @return 分页结果
*/
PageResult<Emp> getPageResult(EmpQueryParam param);
/**
* 保存员工信息
*
* @param emp 员工信息
*/
void saveEmp(Emp emp);
}在EmpServiceImpl中实现该方法:
/**
* 保存员工信息
*
* @param emp 员工信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void saveEmp(Emp emp) {
// 补全基础属性
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
// 保存员工基本信息
save(emp);
// 获取员工工作经历信息
List<EmpExpr> exprList = emp.getExprList();
if (exprList != null && !exprList.isEmpty()) {
exprList.forEach(expr -> {
expr.setEmpId(emp.getId());
});
// 批量保存工作经历信息
exprService.saveBatch(exprList);
}
}由于这里涉及到了批量保存员工的工作经历,所以,需要在程序中添加EmpExprService和其实现类ServiceImpl:
EmpExprService:
package com.itheima.service;
import com.baomidou.Mybatis-Plus.extension.service.IService;
import com.itheima.pojo.EmpExpr;
public interface EmpExprService extends IService<EmpExpr> {
}EmpExprServiceImpl:
package com.itheima.service.impl;
import com.baomidou.Mybatis-Plus.extension.service.impl.ServiceImpl;
import com.itheima.mapper.EmpExprMapper;
import com.itheima.pojo.EmpExpr;
import com.itheima.service.EmpExprService;
import org.springframework.stereotype.Service;
@Service
public class EmpExprServiceImpl extends ServiceImpl<EmpExprMapper, EmpExpr> implements EmpExprService {
}EmpExprMapper:
@Mapper
public interface EmpExprMapper extends BaseMapper<EmpExpr> {
}4.4修改员工
修改员工涉及到2个操作:根据id查询员工用于页面回显和根据id修改员工信息。
先来完成第1步:根据id查询员工。
这里要注意的是,查询回显时不仅仅要查询到员工的基本信息,还要查询到员工的工作经历信息。
在Controller层调用Service层方法获取员工数据:
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
log.info("根据ID查询员工信息,id:{}", id);
// 调用Service层方法获取员工信息
Emp emp = empService.getEmpById(id);
return Result.success(emp);
}在EmpService中添加以下方法:
/**
* 根据id查询员工信息
* @param id 员工id
* @return 查询到的员工信息
*/
Emp getEmpById(Integer id);在EmpServiceImpl中实现该方法:
/**
* 根据id查询员工信息
*
* @param id 员工id
* @return 查询到的员工信息
*/
@Override
public Emp getEmpById(Integer id) {
Emp emp = getById(id);
// 工作经历列表
emp.setExprList(exprService.list(new LambdaQueryWrapper<EmpExpr>().eq(EmpExpr::getEmpId, id)));
return emp;
}接下来,完成点击保存按钮将修改更新到数据库。
这里,又可以拆分为3个小的操作:
- 根据id更新员工的基本信息
- 根据id批量删除员工的工作经历信息
- 批量保存员工的工作经历信息
代码如下:
在Controller层调用Service层方法将数据传给Service层处理:
@PutMapping
public Result update(@RequestBody Emp emp) {
log.info("修改员工信息,员工信息:{}", emp);
// 调用service层方法修改员工信息
empService.updateEmp(emp);
return Result.success();
}在EmpService中添加以下方法:
/**
* 修改员工
* @param emp 员工信息
*/
void updateEmp(Emp emp);在EmpServiceImpl中实现该方法:
/**
* 修改员工
*
* @param emp 员工信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void updateEmp(Emp emp) {
// 根据id更新员工的基本信息
emp.setUpdateTime(LocalDateTime.now());
updateById(emp);
// 根据id批量删除员工的工作经历信息
exprService.remove(new LambdaQueryWrapper<EmpExpr>().eq(EmpExpr::getEmpId, emp.getId()));
// 批量保存员工的工作经历信息
List<EmpExpr> exprList = emp.getExprList();
if (exprList != null && !exprList.isEmpty()) {
exprList.forEach(expr -> expr.setEmpId(emp.getId()));
}
exprService.saveBatch(exprList);
}到这里,修改员工接口就完成啦,接下来咱们再深入一下:小伙伴还记得Web阶段根据id更新员工的Mapper层xml文件是怎么写的吗?是不是下面这样呀?
<update id="update">
update emp
<set>
<if test="username != null and username != ''">
username=#{username},
</if>
<if test="password != null and password != ''">
password=#{password},
</if>
<if test="name != null and name != ''">
name=#{name},
</if>
<if test="gender != null">
gender=#{gender},
</if>
<if test="phone != null and phone != ''">
phone=#{phone},
</if>
<if test="job != null">
job=#{job},
</if>
<if test="salary != null">
salary=#{salary},
</if>
<if test="image != null and image != ''">
image=#{image},
</if>
<if test="entryDate != null">
entry_date=#{entryDate},
</if>
<if test="deptId != null">
dept_id=#{deptId},
</if>
<if test="updateTime != null">
update_time=#{updateTime}
</if>
</set>
where id = #{id}
</update>这里面做了很多的条件判断,那刚刚咱们在EmpServiceImpl的updateEmp方法中调用的updateById()方法能不能实现条件更新呢?来做一个测试:
在APIFox中发起一个请求,修改id等于1的员工数据,json中只保留id、username、phone三个字段:
{
"id": 1,
"username": "shinaian1",
"phone": "13309090301"
}发起请求后,重点查看控制台打印的SQL:
==> Preparing: UPDATE emp SET username=?, phone=?, update_time=? WHERE id=?
==> Parameters: shinaian1(String), 13309090301(String), 2024-09-22T23:57:23.060488600(LocalDateTime), 1(Integer)
<== Updates: 1从执行的SQL中能看出来,Mybatis-Plus自动执行了条件更新,值为null的字段并没有出现在set关键字的后方。
4.5批量删除员工
员工管理页面上每条记录后面的删除按钮和批量删除按钮共用一个方法即可,方法代码如下:
@DeleteMapping
public Result delete(@RequestParam List<Integer> ids) {
log.info("批量删除学生,{}", ids);
// 调用service层方法批量删除员工信息和员工工作经历信息
empService.removeEmpsByIds(ids);
return Result.success();
}在EmpService中添加一个方法:
/**
* 批量删除员工
* @param ids 要删除的员工id
*/
void removeEmpsByIds(List<Integer> ids);在EmpServiceImpl中重写Service层新添加的方法来执行批量删除操作:
/**
* 批量删除员工
*
* @param ids 要删除的员工id
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void removeEmpsByIds(List<Integer> ids) {
// 调用service层方法批量删除员工信息
removeBatchByIds(ids);
// 删除员工工作经历信息
exprService.remove(Wrappers.<EmpExpr>lambdaQuery().in(EmpExpr::getEmpId, ids));
}