目录
在上一篇文章中,我们已经完成了员工管理的列表查询和新增员工功能。今天,我们将继续完善员工管理的剩余功能:删除员工、修改员工,并引入全局异常处理,最后完成员工信息统计的报表接口。
一、删除员工
1.1 需求分析
在员工列表页面,我们可以勾选一个或多个员工前的复选框,然后点击“批量删除”按钮,即可将选中的员工信息删除。如果只勾选一个,效果等同于单个删除。

问题:需要开发两个接口吗(单删和批量删)?
答案:不需要! 我们只需开发一个“批量删除”接口,传入一个员工ID列表。当列表只有一项时,就实现了单个删除的效果。
1.2 接口文档
-
请求方式:
DELETE -
请求路径:
/emps -
请求参数:
ids(数组或列表,例如ids=1&ids=2&ids=3) -
响应结果:统一封装的
Result对象
1.3 实现思路

-
Controller 接收前端传递的
ids(可以是数组或集合)。 -
Service 层调用 Mapper 层,同时删除
emp(员工基本表)和emp_expr(工作经历表)中的数据。 -
为了保证数据一致性,删除操作需要添加事务控制。
1.4 代码实现
1.4.1 Controller 接收参数
在 EmpController 中增加方法:
/**
* 批量删除员工
*/
@DeleteMapping
public Result delete(@RequestParam List<Integer> ids){
log.info("批量删除部门: ids={} ", ids);
empService.deleteByIds(ids);
return Result.success();
}
tips:推荐使用
List<Integer>而非数组,因为集合操作更灵活,如遍历、判空等。注意要加@RequestParam,让 Spring 知道参数来自请求参数(而非 JSON 体)。
1.4.2 Service 层
接口 EmpService 添加方法:
/**
* 批量删除员工
*/
void deleteByIds(List<Integer> ids);
实现类 EmpServiceImpl:
@Transactional
@Override
public void deleteByIds(List<Integer> ids) {
//1. 根据ID批量删除员工基本信息
empMapper.deleteByIds(ids);
//2. 根据员工的ID批量删除员工的工作经历信息
empExprMapper.deleteByEmpIds(ids);
}
1.4.3 Mapper 层
EmpMapper.java:
/**
* 批量删除员工信息
*/
void deleteByIds(List<Integer> ids);
EmpMapper.xml(使用 <foreach> 动态拼接 SQL):
<!--批量删除员工信息-->
<delete id="deleteByIds">
delete from emp where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
同理,EmpExprMapper 也实现类似方法:
/**
* 根据员工的ID批量删除工作经历信息
*/
void deleteByEmpIds(List<Integer> empIds);
<!--根据员工的ID批量删除工作经历信息-->
<delete id="deleteByEmpIds">
delete from emp_expr where emp_id in
<foreach collection="empIds" item="empId" open="(" close=")" separator=",">
#{empId}
</foreach>
</delete>
1.5 测试
-
使用 Apifox 或 Postman 发送 DELETE 请求,如
/emps?ids=1&ids=2。 -

-
观察控制台打印的 SQL 语句,确认执行成功。
-

-
前后端联调:在浏览器中勾选员工并点击删除,查看数据是否被移除。
-

二、修改员工
修改员工分为两步:查询回显(将已有数据展示在表单中)和保存修改(将更新后的数据提交到数据库)。
2.1 查询回显
2.1.1 需求
点击员工列表的“编辑”按钮,弹出编辑窗口,并自动填充该员工的基本信息和工作经历。

2.1.2 实现思路
我们需要查询 员工基本信息 + 工作经历列表。通过一条左连接 SQL 可以同时查出两张表的数据,然后在 MyBatis 中利用 <resultMap> 封装成包含 List<EmpExpr> 的 Emp 对象。

SQL 示例:
select e.*,
ee.id ee_id,
ee.begin ee_begin,
ee.end ee_end,
ee.company ee_company,
ee.job ee_job
from emp e left join emp_expr ee on e.id = ee.emp_id where e.id = 39;
2.1.3 代码实现
Controller:
/**
* 查询回显
*/
@GetMapping("/{id}")
public Result getInfo(@PathVariable Integer id){
log.info("根据id查询员工的详细信息");
Emp emp = empService.getInfo(id);
return Result.success(emp);
}
Service:
/**
* 根据ID查询员工的详细信息
*/
Emp getInfo(Integer id);
@Override
public Emp getInfo(Integer id) {
return empMapper.getById(id);
}
Mapper(重点):
-
定义
resultMap手动映射字段,并使用<collection>处理一对多关系。 -
用
getById方法执行查询。
/**
* 根据ID查询员工详细信息
*/
Emp getById(Integer id);
<!--自定义结果集ResultMap-->
<resultMap id="empResultMap" type="com.itheima.pojo.Emp">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
<result column="name" property="name" />
<result column="gender" property="gender" />
<result column="phone" property="phone" />
<result column="job" property="job" />
<result column="salary" property="salary" />
<result column="image" property="image" />
<result column="entry_date" property="entryDate" />
<result column="dept_id" property="deptId" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
<!--封装exprList-->
<collection property="exprList" ofType="com.itheima.pojo.EmpExpr">
<id column="ee_id" property="id"/>
<result column="ee_company" property="company"/>
<result column="ee_job" property="job"/>
<result column="ee_begin" property="begin"/>
<result column="ee_end" property="end"/>
<result column="ee_empid" property="empId"/>
</collection>
</resultMap>
<!--根据ID查询员工的详细信息-->
<select id="getById" resultMap="empResultMap">
select e.*,
ee.id ee_id,
ee.emp_id ee_empid,
ee.begin ee_begin,
ee.end ee_end,
ee.company ee_company,
ee.job ee_job
from emp e left join emp_expr ee on e.id = ee.emp_id
where e.id = #{id}
</select>
resultType vs resultMap:如果查询结果的列名与实体属性名完全一致,用
resultType即可。当列名与属性名不一致,或需要处理嵌套对象/集合时,必须用resultMap手动封装。
2.1.4 测试
-
使用 Apifox 调用 GET
/emps/{id},观察返回的 JSON 是否包含完整信息。 -
联调测试:点击“编辑”按钮,检查表单是否正确回显。
2.2 保存修改
2.2.1 需求
用户修改表单数据后点击“保存”,将新数据提交到服务端,更新数据库中的员工信息和相关的工作经历。
2.2.2 实现思路

-
更新
emp表的基本信息(只更新有变化的字段,利用动态 SQL)。 -
删除该员工原有的所有工作经历(
emp_expr表)。 -
重新插入新的工作经历列表(前端提交的数据)。
2.2.3 代码实现
Controller:
/**
* 更新员工信息
*/
@PutMapping
public Result update(@RequestBody Emp emp){
log.info("修改员工信息, {}", emp);
empService.update(emp);
return Result.success();
}
Service:
/**
* 更新员工信息
* @param emp
*/
void update(Emp emp);
@Transactional
@Override
public void update(Emp emp) {
//1. 根据ID更新员工基本信息
emp.setUpdateTime(LocalDateTime.now());
empMapper.updateById(emp);
//2. 根据员工ID删除员工的工作经历信息 【删除老的】
empExprMapper.deleteByEmpIds(Arrays.asList(emp.getId()));
//3. 新增员工的工作经历数据 【新增新的】
Integer empId = emp.getId();
List<EmpExpr> exprList = emp.getExprList();
if(!CollectionUtils.isEmpty(exprList)){
exprList.forEach(empExpr -> empExpr.setEmpId(empId));
empExprMapper.insertBatch(exprList);
}
}
Mapper:
EmpMapper.updateById 使用 <set> 和 <if> 实现动态更新,只更新非空字段:
/**
* 更新员工基本信息
*/
void updateById(Emp emp);
<!--根据ID更新员工信息-->
<update id="updateById">
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>
2.2.4 测试
-
Apifox 发送 PUT 请求,携带完整的
EmpJSON 对象。 -
查看数据库中的数据是否更新,以及工作经历是否替换成功。
-
前端联调:修改员工信息并保存,确认页面和数据库一致。
三、异常处理
在开发过程中,难免会遇到各种异常,比如数据库唯一约束冲突、空指针等。如果不处理,Spring Boot 会返回默认的错误信息,格式不统一,前端无法解析。
3.1 问题演示
比如修改员工时,输入一个已存在的手机号(数据库 phone 字段有唯一约束),会抛出 DuplicateKeyException。此时后端返回的 JSON 并不是我们约定的 Result 格式,而是类似这样的:

前端无法统一处理,用户体验差。
3.2 解决方案:全局异常处理器
我们不再在每个 Controller 方法中写
try-catch,而是定义一个 全局异常处理器,统一捕获所有异常,并返回规范的Result对象。
3.3 实现全局异常处理器
只需创建一个类,加上 @RestControllerAdvice,并在方法上使用 @ExceptionHandler 指定要捕获的异常类型:
@RestControllerAdvice
public class GlobalExceptionHandler {
//处理异常
@ExceptionHandler
public Result ex(Exception e){//方法形参中指定能够处理的异常类型
e.printStackTrace();//打印堆栈中的异常信息
//捕获到异常之后,响应一个标准的Result
return Result.error("对不起,操作失败,请联系管理员");
}
}
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
处理异常的方法返回值会转换为json后再响应给前端
注意:你还可以细化捕获特定异常,如
DuplicateKeyException,然后返回更具体的提示(如“手机号已存在”)。
有了全局异常处理器,所有异常都会被拦截,并返回标准的 Result 格式,前端可以正常解析并给出友好提示。
以上就是全局异常处理器的使用,主要涉及到两个注解:
@RestControllerAdvice //表示当前类为全局异常处理器
@ExceptionHandler //指定可以捕获哪种类型的异常进行处理
四、员工信息统计
现在,我们来实现两个统计报表接口:员工职位统计和员工性别统计。这些数据通常用于 ECharts (https://echarts.apache.org/zh/index.html)等图表组件展示,后端只需提供 JSON 数据即可。
4.1 职位统计
4.1.1 需求
统计每个职位的员工人数,返回一个包含职位名称列表和对应人数列表的对象,例如:

4.1.2 实现
定义封装结果对象JobOption
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JobOption {
private List jobList;
private List dataList;
}
Controller:
@Slf4j
@RequestMapping("/report")
@RestController
public class ReportController {
@Autowired
private ReportService reportService;
/**
* 统计各个职位的员工人数
*/
@GetMapping("/empJobData")
public Result getEmpJobData(){
log.info("统计各个职位的员工人数");
JobOption jobOption = reportService.getEmpJobData();
return Result.success(jobOption);
}
}
Service:
public interface ReportService {
/**
* 统计各个职位的员工人数
* @return
*/
JobOption getEmpJobData();
}
@Service
public class ReportServiceImpl implements ReportService {
@Autowired
private EmpMapper empMapper;
@Override
public JobOption getEmpJobData() {
List<Map<String,Object>> list = empMapper.countEmpJobData();
List<Object> jobList = list.stream().map(dataMap -> dataMap.get("pos")).toList();
List<Object> dataList = list.stream().map(dataMap -> dataMap.get("total")).toList();
return new JobOption(jobList, dataList);
}
}
Mapper:
/**
* 统计各个职位的员工人数
*/
@MapKey("pos")
List<Map<String,Object>> countEmpJobData();
如果查询的记录往Map中封装,可以通过@MapKey注解指定返回的map中的唯一标识是那个字段。【也可以不指定】
<!-- 统计各个职位的员工人数 -->
<select id="countEmpJobData" resultType="java.util.Map">
select
(case job when 1 then '班主任'
when 2 then '讲师'
when 3 then '学工主管'
when 4 then '教研主管'
when 5 then '咨询师'
else '其他' end) pos,
count(*) total
from emp group by job
order by total
</select>
这里使用了 SQL 的 CASE 语句将数字编码转换为可读的职位名称,并按数量排序。
case流程控制函数:
语法一:case when cond1 then res1 [ when cond2 then res2 ] else res end ;
含义:如果 cond1 成立, 取 res1。 如果 cond2 成立,取 res2。 如果前面的条件都不成立,则取 res。
语法二(仅适用于等值匹配):case expr when val1 then res1 [ when val2 then res2 ] else res end ;
含义:如果 expr 的值为 val1 , 取 res1。 如果 expr 的值为 val2 ,取 res2。 如果前面的条件都不成立,则取 res。
4.1.3 测试
-
Apifox 调用 GET
/report/empJobData,查看返回的数据。 -
前端联调:将数据传入 ECharts 的饼图或柱状图,验证展示效果。
4.2 性别统计
4.2.1 需求
统计男性和女性员工人数,返回格式通常为:

4.2.2 实现
Controller:
/**
* 统计员工性别信息
*/
@GetMapping("/empGenderData")
public Result getEmpGenderData(){
log.info("统计员工性别信息");
List<Map> genderList = reportService.getEmpGenderData();
return Result.success(genderList);
}
Service:
/**
* 统计员工性别信息
*/
List<Map> getEmpGenderData();
@Override
public List<Map> getEmpGenderData() {
return empMapper.countEmpGenderData();
}
Mapper:
/**
* 统计员工性别信息
*/
@MapKey("name")
List<Map> countEmpGenderData();
<!-- 统计员工的性别信息 -->
<select id="countEmpGenderData" resultType="java.util.Map">
select
if(gender = 1, '男', '女') as name,
count(*) as value
from emp group by gender ;
</select>
这里使用了 MySQL 的 IF 函数:如果 gender=1 则返回 '男',否则返回 '女'。group by gender 会分别统计。
if函数语法:
if(条件, 条件为true取值, 条件为false取值)ifnull函数语法:
ifnull(expr, val1)如果expr不为null,取自身,否则取val1
4.2.3 测试
-
Apifox 调用 GET
/report/empGenderData,确认返回的name和value键值对正确。 -
前端联调,将数据传递给饼图组件(例如 ECharts 的
pie系列),即可展示性别比例。
总结
今天我们完成了员工管理的删除、修改、全局异常处理以及信息统计功能。回顾重点:
-
删除员工:利用
List<Integer>接收多个 ID,批量删除两张表,并通过@Transactional保证事务一致性。 -
修改员工:分为“查询回显”和“保存修改”。查询时使用左连接和
resultMap封装一对多关系;更新时采用动态 SQL 和“先删后增”的策略维护工作经历。 -
全局异常处理:使用
@RestControllerAdvice+@ExceptionHandler统一捕获异常,返回规范的Result,提升用户体验。 -
信息统计:通过 SQL 分组和聚合函数计算职位和性别分布,返回前端所需的数据结构,用于图表渲染。
本文所用技术栈:Spring Boot 2.x + MyBatis + MySQL + Apifox + ECharts



2155

被折叠的 条评论
为什么被折叠?



