[toc]
| 单词 | 词性 | 音标 | 含义 |
|---|---|---|---|
| RESTful | adj. | /ˈrestfl/ | REST 风格(用 HTTP 方法语义表达操作:GET 查询、POST 新增、PUT 修改、DELETE 删除) |
| Controller | n. | /kənˈtrəʊlə(r)/ | 控制器(表现层,接收 HTTP 请求并返回 JSON) |
| Request Body | n. | /rɪˈkwest ˈbɒdi/ | 请求体(@RequestBody 把前端传来的 JSON 转成 Java 对象) |
| Path Variable | n. | /pɑːθ ˈveəriəbl/ | 路径变量(@PathVariable 取出 URL 路径中的参数,如 /emp/1 中的 1) |
Lecture14-综合实战-员工管理模块
一、实战目标
把第 1~11 章的知识点串起来,做一个员工管理 RESTful 接口模块,包含:
| 功能 | 接口 | 使用的知识点 |
|---|---|---|
| 分页 + 多条件查询 | GET /emp/page | 分页插件 + LambdaQueryWrapper |
| 按主键查询 | GET /emp/{id} | selectById |
| 新增员工 | POST /emp | insert + 自动填充 |
| 修改员工 | PUT /emp | updateById(配合乐观锁) |
| 删除员工 | DELETE /emp/{id} | 逻辑删除 |
| 按部门统计人数 | GET /emp/count/byDept | groupBy + selectMaps |
数据库表使用最终形态(整合第 9、10 章的字段):
CREATE TABLE t_emp (
emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID',
emp_no VARCHAR(20) NOT NULL COMMENT '员工编号',
emp_name VARCHAR(50) NOT NULL COMMENT '员工姓名',
emp_salary DECIMAL(10,2) COMMENT '员工工资',
emp_birthday DATE COMMENT '出生日期',
phone_num VARCHAR(20) COMMENT '手机号',
dept_id INT COMMENT '部门ID',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0未删 1已删',
create_time DATETIME COMMENT '创建时间',
update_time DATETIME COMMENT '更新时间',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'
) COMMENT '员工表';
二、工程结构
module-mp-demo
├── pom.xml
└── src/main/java/com/atguigu/mp
├── ModuleMpApplication.java # 主启动类
├── config/
│ ├── MybatisPlusConfig.java # 分页插件 + 乐观锁插件
│ └── MyMetaObjectHandler.java # 自动填充处理器
├── entity/
│ └── Employee.java # 实体类
├── mapper/
│ └── EmployeeMapper.java # Mapper 接口
├── service/
│ ├── EmployeeService.java # Service 接口
│ └── impl/
│ └── EmployeeServiceImpl.java # Service 实现
└── controller/
└── EmployeeController.java # 控制器
三、各层完整代码
1、实体类 Employee
package com.atguigu.mp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
@Data
@TableName("t_emp")
public class Employee {
@TableId(type = IdType.AUTO)
private Integer empId;
private String empNo;
private String empName;
private BigDecimal empSalary;
private Date empBirthday;
private String phoneNum;
private Integer deptId;
@TableLogic
private Integer deleted;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@Version
private Integer version;
}
2、Mapper 接口
package com.atguigu.mp.mapper;
import com.atguigu.mp.entity.Employee;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
3、Service 接口与实现
package com.atguigu.mp.service;
import com.atguigu.mp.entity.Employee;
import com.baomidou.mybatisplus.extension.service.IService;
public interface EmployeeService extends IService<Employee> {
}
package com.atguigu.mp.service.impl;
import com.atguigu.mp.entity.Employee;
import com.atguigu.mp.mapper.EmployeeMapper;
import com.atguigu.mp.service.EmployeeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}
4、配置类
package com.atguigu.mp.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
package com.atguigu.mp.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
5、控制器 EmployeeController
package com.atguigu.mp.controller;
import com.atguigu.mp.entity.Employee;
import com.atguigu.mp.service.EmployeeService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/emp")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
// ===== 1. 分页 + 多条件查询:GET /emp/page?pageNum=1&pageSize=3&name=张&deptId=1&minSalary=5000 =====
@GetMapping("/page")
public Page<Employee> page(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "3") Integer pageSize,
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer deptId,
@RequestParam(required = false) BigDecimal minSalary,
@RequestParam(required = false) BigDecimal maxSalary) {
Page<Employee> page = new Page<>(pageNum, pageSize);
// 条件可选:值为 null 时自动忽略
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.like(name != null, Employee::getEmpName, name)
.eq(deptId != null, Employee::getDeptId, deptId)
.ge(minSalary != null, Employee::getEmpSalary, minSalary)
.le(maxSalary != null, Employee::getEmpSalary, maxSalary)
.orderByDesc(Employee::getEmpSalary);
return employeeService.page(page, wrapper);
}
// ===== 2. 按主键查询:GET /emp/1 =====
@GetMapping("/{id}")
public Employee getById(@PathVariable Integer id) {
return employeeService.getById(id);
}
// ===== 3. 新增员工:POST /emp(请求体 JSON)=====
@PostMapping
public boolean save(@RequestBody Employee employee) {
// create_time / update_time 自动填充;主键自增回填
return employeeService.save(employee);
}
// ===== 4. 修改员工:PUT /emp(请求体 JSON,携带 version)=====
@PutMapping
public boolean update(@RequestBody Employee employee) {
// 乐观锁生效:前端必须回传 version,version 对不上则更新失败
boolean updated = employeeService.updateById(employee);
if (!updated) {
throw new RuntimeException("数据已被他人修改,请刷新后重试");
}
return true;
}
// ===== 5. 删除员工:DELETE /emp/1(逻辑删除)=====
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return employeeService.removeById(id); // 实际执行 UPDATE deleted=1
}
// ===== 6. 按部门统计人数:GET /emp/count/byDept =====
@GetMapping("/count/byDept")
public List<Map<String, Object>> countByDept() {
// 统计类查询:查询列不是实体属性,用 QueryWrapper + selectMaps
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select("dept_id", "count(*) as empCount")
.groupBy("dept_id");
return employeeService.listMaps(wrapper);
}
}
四、测试
工程中加入 spring-boot-starter-web 依赖后,启动主启动类,用浏览器 / Postman / IDEA HTTP Client 测试:
| 测试项 | 请求 | 预期结果 |
|---|---|---|
| 分页查询 | GET /emp/page?pageNum=1&pageSize=3 | 返回 Page JSON:records 3 条、total 总数 |
| 条件查询 | GET /emp/page?name=张 | 只返回姓名含「张」的员工 |
| 新增 | POST /emp + JSON | 返回 true,表中新增一条,create_time 自动填充 |
| 修改 | PUT /emp + JSON(带 version) | 返回 true;再查 version 已 +1 |
| 删除 | DELETE /emp/1 | 返回 true;再查该员工查不到(deleted=1) |
| 部门统计 | GET /emp/count/byDept | 返回 [{dept_id:1, empCount:2}, ...] |
小提示:修改接口必须回传
version,否则乐观锁条件不满足更新失败——这正是乐观锁设计的体现。
五、全教程知识地图
| 知识点 | 一句话总结 | 章节 |
|---|---|---|
| 环境搭建 | 依赖 + 数据源 + 实体 + Mapper,跑通第一个查询 | 第 2 章 |
| 实体注解 | @TableName / @TableId / @TableField 让映射清晰可控 | 第 3 章 |
| BaseMapper | 内置增删改查,一行 SQL 都不用写 | 第 4 章 |
| QueryWrapper | 字符串列名拼条件 | 第 5 章 |
| LambdaQueryWrapper | 方法引用拼条件,编译期安全,推荐 | 第 6 章 |
| 分页 | 插件 + Page 对象,自动 count + limit | 第 7 章 |
| UpdateWrapper | 局部更新指定列 | 第 8 章 |
| 逻辑删除 / 自动填充 | 删除不真删、时间自动维护 | 第 9 章 |
| 乐观锁 | version 校验,并发不丢更新 | 第 10 章 |
| Service 层 | IService + 链式操作,业务代码更简洁 | 第 11 章 |
六、常见问题速查(全教程汇总)
| 现象 | 原因与解决 |
|---|---|
| 启动报错 | mybatis-plus-spring-boot3-starter 用 3.5.9+;确认依赖已导入 |
| 查不到数据 / 表不存在 | 检查 @TableName;检查逻辑删除配置是否把 deleted=0 条件加上了 |
| 分页不生效 | 确认 MybatisPlusInterceptor 配置类被扫描 |
| 更新失败返回 0 行 | 检查乐观锁 version 是否有值;检查条件是否正确 |
selectCount 类型不匹配 | 3.5.x 返回 Long |
| 插入后主键为 null | 确认 @TableId(type = IdType.AUTO) 且表主键自增 |
| 自定义 SQL 场景 | 本教程未覆盖;实际项目中少量复杂 SQL 可在 Mapper 接口中用注解/XML 补充(后续课程讲解) |
七、结语
到这里,MyBatis-Plus 的常用操作就学完了。回顾核心心法:
单表操作不写 SQL:继承 BaseMapper / IService,方法开箱即用; 动态条件用 Wrapper:LambdaQueryWrapper + 可选条件,一个方法吃遍各种查询; 通用能力靠插件:分页、乐观锁、逻辑删除、自动填充,配一次全表生效。
把这些能力组合起来,一个模块的增删改查接口几十分钟就能写完。后续学 Spring Boot、Spring Cloud 时,这套数据访问方式会一直伴随你。