【JavaWeb】Tlias智能学习辅助系统|后端Web实战(员工管理)

目录

一、删除员工

1.1 需求分析

1.2 接口文档

1.3 实现思路

1.4 代码实现

1.4.1 Controller 接收参数

1.4.2 Service 层

1.4.3 Mapper 层

1.5 测试

二、修改员工

2.1 查询回显

2.1.1 需求

2.1.2 实现思路

2.1.3 代码实现

2.1.4 测试

2.2 保存修改

2.2.1 需求

2.2.2 实现思路

2.2.3 代码实现

2.2.4 测试

三、异常处理

3.1 问题演示

3.2 解决方案:全局异常处理器

3.3 实现全局异常处理器

四、员工信息统计

4.1 职位统计

4.1.1 需求

4.1.2 实现

4.1.3 测试

4.2 性别统计

4.2.1 需求

4.2.2 实现

4.2.3 测试

总结


在上一篇文章中,我们已经完成了员工管理的列表查询新增员工功能。今天,我们将继续完善员工管理的剩余功能:删除员工修改员工,并引入全局异常处理,最后完成员工信息统计的报表接口。


一、删除员工

1.1 需求分析

在员工列表页面,我们可以勾选一个或多个员工前的复选框,然后点击“批量删除”按钮,即可将选中的员工信息删除。如果只勾选一个,效果等同于单个删除。

问题:需要开发两个接口吗(单删和批量删)?
答案:不需要! 我们只需开发一个“批量删除”接口,传入一个员工ID列表。当列表只有一项时,就实现了单个删除的效果。

1.2 接口文档

  • 请求方式DELETE

  • 请求路径/emps

  • 请求参数ids(数组或列表,例如 ids=1&ids=2&ids=3

  • 响应结果:统一封装的 Result 对象

1.3 实现思路

  1. Controller 接收前端传递的 ids(可以是数组或集合)。

  2. Service 层调用 Mapper 层,同时删除 emp(员工基本表)和 emp_expr(工作经历表)中的数据。

  3. 为了保证数据一致性,删除操作需要添加事务控制。

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 实现思路

  1. 更新 emp 表的基本信息(只更新有变化的字段,利用动态 SQL)。

  2. 删除该员工原有的所有工作经历(emp_expr 表)。

  3. 重新插入新的工作经历列表(前端提交的数据)。

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 请求,携带完整的 Emp JSON 对象。

  • 查看数据库中的数据是否更新,以及工作经历是否替换成功。

  • 前端联调:修改员工信息并保存,确认页面和数据库一致。


三、异常处理

在开发过程中,难免会遇到各种异常,比如数据库唯一约束冲突、空指针等。如果不处理,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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值