当前位置: 首页 > news >正文

员工管理-批量删除和修改、全局异常处理器和员工信息统计Ecahrs-数据转换格式

员工管理-批量删除

批量删除一是要利用<foreach></foreach>实现我们数组的sql语句拼接

并且逻辑外键中,我们不光要删除员工表的数据,还要删除员工工作经历表的数据(都是根据员工id)

1.请求参数的接收

既然是批量删除,所以请求参数一般都是ids=1,2,3...这种一般我们在Controller层拿数组或集合接

既然谈到批量,那么不然想到我们的xml配置文件中可以用循环<foreach></foreach>的方式

2.Controller层-数组/集合封装参数

方法一:

用数组来接收,如果前端参数名和形参数组名一样,则无需@RequestParam绑定

方法二(推荐):

用集合在来接收,无论如何都要加@RequestParam绑定

3.Service层

注意逻辑外键的处理即可,也就是员工表和员工经历表都是要协同着删除,并且他们处于一个事务@Transational当中,要么全成功要么全失败。

4.Mapper层

根据原sql语句配置xml

delete from emp where id in (1,2,3....)

xml配置如下(员工经历表类似)

<delete id="delete"> delete from emp where id in <foreach collection="ids" item="id" separator=","open="(" close=")"> #{id} </foreach> </delete>

员工管理-修改

1.查询回显:MyBatis 一对多查询:使用 ResultMap 实现员工信息 + 工作经历封装

(1)业务场景

一个员工对应多条工作经历(一对多关系)。
查询员工详细信息时,需要将员工基本信息关联的工作经历列表(EmpExpr)封装到一个Emp对象中。

  • Emp对象中包含一个List<EmpExpr> exprList属性

  • EmpExpr是独立的工作经历实体,外键emp_id指向emp.id


(2)SQL 查询的问题

sql

select e.*, ee.id ee_id, ee.emp_id ee_empid, ee.company ee_company, ee.job ee_job, ee.begin ee_begin, ee.end ee_end from emp e left join emp_expr ee on e.id = ee.emp_id where e.id = #{id}

如果一个员工有两条工作经历,这条 SQL 会返回两行记录

e.ide.nameee.idee.company
1张三10阿里
1张三11腾讯

但我们希望的Emp对象结构是:

json

{ "id": 1, "name": "张三", "exprList": [ { "id": 10, "company": "阿里" }, { "id": 11, "company": "腾讯" } ] }

直接使用resultType无法自动完成这种一对多封装,因此需要自定义resultMap


(3)解决方案:自定义 ResultMap

[1] Mapper 接口

java

@Mapper public interface EmpMapper { Emp getById(Integer id); }
[2] XML 映射文件(核心)

xml

<resultMap id="empResultMap" type="com.itheima.pojo.Emp"><!-- 主键必须用 id 标签 --> <id column="id" property="id" /> <!-- 普通字段用 result 标签 --> <result column="username" property="username" /> <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> <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>

(4)关键点解释

[1]resultMap中的type

xml

<resultMap id="empResultMap" type="com.itheima.pojo.Emp">

表示最终要封装的 Java 对象类型是Emp

[2] <id>vs<result>
标签说明
<id>映射数据库主键列,MyBatis 会用它来区分不同对象
<result>映射普通字段

在一对多场景中,<id>帮助 MyBatis 判断哪些行属于同一个主对象,否则可能出现重复或死循环。

[3]<collection>标签

xml

<collection property="exprList" ofType="com.itheima.pojo.EmpExpr">
属性说明
propertyEmp 对象中存放集合的属性名
ofType集合中每个元素的类型(泛型)

其内部子标签和普通字段一样,column是 SQL 返回的列名,propertyEmpExpr的属性名。

[4] 为什么给工作经历字段起了别名?

sql

select ee.id ee_id, ee.company ee_company
  • emp表和emp_expr表中都有id字段,不取别名会冲突

  • 方便在resultMap中通过ee_idee_company区分来源


(5)总结(resultType和resultMap)

场景使用方式
单表查询 / 字段名和属性名一致resultType+ 开启驼峰映射
字段名和属性名不一致resultMap@Results
一对多 / 一对一 / 集合嵌套必须使用resultMap+<collection>

核心思想
SQL 负责多表关联查出“扁平化”的数据,resultMap负责将扁平数据“重组”为带有集合的层次化对象。


(6)踩过的坑(仅供参考)

  1. 忘记设置ofType→ 集合里是 Map,不是 EmpExpr 对象

  2. <id>标签没有定义→ 集合可能重复或顺序错乱

  3. SQL 列名和resultMapcolumn不一致→ 值取不到,属性为 null

  4. 主对象字段也用<result>不用<id>→ 轻微性能问题,但通常无感

2.修改数据—<set>

{
"id": 2,
"username": "zhangwuji",
"name": "张无忌",
"gender": 1,
"image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
"job": 2,
"salary": 8000,
"entryDate": "2015-01-01",
"deptId": 2,
"exprList": [
{
"begin": "2012-07-01",
"end": "2015-06-20",
"company": "中软国际股份有限公司",
"job": "java开发"
},
{
"begin": "2015-07-01",
"end": "2019-03-03",
"company": "百度科技股份有限公司",
"job": "java开发"
}
]
}

对于正常员工表基本信息的修改不进行过多赘述

主要强调修改员工工作经历信息则分为-先删再加

(1)Controller层

@PutMapping public Result update(@RequestBody Emp emp){ log.info("修改员工信息:{}", emp); empService.update(emp); return Result.success(); }

@RequestBody接受前端json格式数据

(2)Service层

@Override @Transactional(rollbackFor = Exception.class) public void update(Emp emp) { //1.根据ID修改员工基本信息 emp.setUpdateTime(LocalDateTime.now()); empMapper.update(emp); //2.根据ID修改员工工作经历信息 empExprMapper.deleteByEmpId(emp.getId());---先删 List<EmpExpr> exprList = emp.getExprList(); if (!CollectionUtils.isEmpty(exprList)) { //要先给每个员工工作经历的empId,因为前端传来的json数据是不包含empId的,这里需要我们业务层自己处理exprList.forEach(empExpr -> empExpr.setEmpId(emp.getId()));empExprMapper.insertBatch(exprList);--批量加入,调用之前我们写过的mapper接口 } }

(3)Mapper层

<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="createTime != null"> create_time = #{createTime}, </if> </set> where id = #{id} </update>

我们在这里需要动态的根据传入进来的json数据来进行更新操作,这里类似于条件查询

所以我们需要引入<where></where>这个标签,并且对于String类型不光要判断是否为null,还需要判断是否为空字符串

(4)<where>vs<set>对比

标签作用解决的问题
<where>动态生成 WHERE 子句自动去掉第一个ANDOR
<set>动态生成 SET 子句自动去掉最后一个多余的逗号

但这个有个非常有意思的点,就是where的确是可以根据情况删除的,因为where属于sql语句中的可选条件,但是set其实是DML语句中不可省略的即

update 表名 set 更新内容 where 条件筛选

解决办法这里有一种即在<set>里加一个“永远为 true 但无副作用”的条件:

我们的updateTime已经在Service层进行赋值了,所以无论如何都可以在这里加上

<set>update_time = #{updateTime},<if test="username != null and username != ''"> username = #{username}, </if> <!-- 其他字段 --> </set>

全局异常处理器

为什么要使用全局异常处理器?

设置全局异常处理器,是为了把“异常处理”从业务代码中抽离出来,避免 try-catch 散落在各处,让代码专注于业务逻辑。

我们在Controller层统一的捕获异常,如果在每个Controller层的每个方法都是用try catch来捕获响应异常,那代码就会变得十分臃肿难以维护,因此我们使用全局异常处理器来捕获异常

如何定义全局异常处理器?

  • 定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。

  • 在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。

@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { // 处理异常 @ExceptionHandler public Result ex(Exception e){// 方法形参中指定能够处理的异常类型 log.error("程序出错",e); // 捕获到异常之后,响应一个标准的Result return Result.error("出错了请联系管理员"); } }

核心注解

注解作用位置
@RestControllerAdvice标识这是一个全局异常处理类,同时自动将返回值转成 JSON类上
@ExceptionHandler标识方法处理哪种异常方法上

记忆公式

@RestControllerAdvice= @ControllerAdvice +@ResponseBody
@ExceptionHandler(异常类.class)= 这个方法专门处理这种异常

执行流程

text

Controller 抛出异常 ↓ Spring 拦截异常,根据异常类型匹配 ↓ 找到对应的 @ExceptionHandler 方法 ↓ 执行该方法,返回 Result 对象 ↓ 自动转成 JSON 返回给前端

分析设计异常处理方法

我们需要分析需要捕获的异常类型并且给前端合适的提示信息

@ExceptionHandler(DuplicateKeyException.class) public Result handlerDuplicateKeyException(DuplicateKeyException e){ // 1. 记录错误日志,方便开发人员排查问题(控制台会输出红色的堆栈信息) log.error("数据库操作出错", e); // 2. 获取异常的详细消息 // 示例:Duplicate entry '13309090027' for key 'emp.phone' String msg = e.getMessage(); // 3. 找到 "Duplicate entry" 这个字符串开始的位置 //目的:截取从异常描述开始的部分,去掉前面的类名等信息int i = msg.indexOf("Duplicate entry"); // 4. 从 "Duplicate entry" 开始截取到最后 // 截取后:Duplicate entry '13309090027' for key 'emp.phone' String errMsg = msg.substring(i); // 5.按空格分割字符串// 分割后得到数组:["Duplicate", "entry", "'13309090027'", "for", "key", "'emp.phone'"] String[] s = errMsg.split(" "); // 6. 取数组的第3个元素(索引2),就是重复的具体值 // 注意:数组索引0是"Duplicate",1是"entry",2就是那个被单引号包着的重复值 // 取到:'13309090027' String phone = s[2]; // 7. 把重复的值拼接到提示语中,返回给前端用户 // 用户看到:'13309090027'已存在! return Result.error(phone + "已存在!"); }

这里的处理方法只是一个样例,真实的业务逻辑肯定有更为细分的比如:用户名重复,身份证重复等等

异常捕获方法规则

异常处理器的捕获规则是从下往上(从子类到父类)查找是否有方法进行捕获

员工信息统计Echars

对于这些图形报表的开发,其实呢,都是基于现成的一些图形报表的组件开发的,比如:Echarts、HighCharts等。

而报表的制作,主要是前端人员开发,引入对应的组件(比如:ECharts)即可。 服务端开发人员仅为其提供数据即可。

官网:https://echarts.apache.org/zh/index.html

报表制作分析

在网站中找到我们要制作的报表类型,这里以基础柱状图为例

左侧为数据类型,不难看出来即x轴即数据目录名,y轴对应每列的数据值

给前端返回的json格式数据即

{
"code": 1,
"msg": "success",
"data": {
"jobList": ["教研主管","学工主管","其他","班主任","咨询师","讲师"],
"dataList": [1,1,2,6,8,13]
}
}

给前端的返回为两个集合,对应了我们基础柱状图的xy轴数据,所以我们需要再创建一个类进行数据封装

报表制作实现-sql语句-case

创建JobOption类封装数据

/**
* 员工职位人数统计
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JobOption {
private List jobList; //职位列表
private List dataList; //人数列表
}

由于我们的job在数据库层面记录的是Integer类型,而我们想返回给前端的是与之相对应的职称名字,所以我们可以使用sql语句提供的case语法,这里我们给出case语句的两种写法

写法语法适用场景示例
简单 CASECASE字段WHEN 值 THEN 结果 ... (ELSE 值)可选END字段等于某个固定值CASEjobWHEN 1 THEN '班主任' WHEN 2 THEN '讲师' END
搜索 CASECASE WHEN条件THEN 结果 ... (ELSE 值)可选END范围判断、复杂条件CASE WHENage < 18THEN '未成年' WHEN age < 60 THEN '成年' END

编写sql语句统计每个职位的人数

-- 根据职位进行统计 简单CASE select casejobwhen 1 then '班主任' when 2 then '讲师' when 3 then '学工主管' when 4 then '教研主管' when 5 then '咨询师' else '其他' end, count(*) num from emp group by job order by num;

搜索CASE

select case whenjob = 1then '班主任' when job = 2 then '讲师' when job = 3 then '学工主管' when job = 4 then '教研主管' when job = 5 then '咨询师' else '其他' end, count(*) num from emp group by job order by num;

(1)Mapper层

/** * 统计各个职位的员工人数 */ List<Map<String,Object>> countEmpJobData()

xml配置

<!-- 统计每个职位的数量--> <!-- 统计各个职位的员工人数 --> <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(*)totalfrom emp group by job order by total </select>
为什么是List<Map<String, Object>>
  • List:SQL 查询出来的是多行数据,所以用List装每一行。

  • Map:每一行数据有多个列,用Map的键值对来存——列名作 Key,列值作 Value

这里如果你的resultType是map集合,则每一项的(Key,value)都是(列名,值)

  • String:列名是字符串(比如"pos""total"),所以 Key 的类型是String

  • Object:列值可能是字符串(职位名),也可能是数字(人数),所以 Value 的类型用Object统一接收。


一句话总结:
List管“行数”,Map管“列数”,String管“列名”,Object管“列值”。
这是一种通用的、灵活的数据结构,专门用来接收那些没有对应 Java 实体类的查询结果(比如统计报表)。

比如我们这项查询出来的List<Map<String,Object>>就是这样的

[ (pos,班主任), (total,7), (pos,讲师), (total,13), (pos,咨询师), (total,8) ]

(2)Service层

@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); } }

这里用stream流提取,把list集合的每一项map集合拿出来,并且提取其列名所对应的值进行封装

(3)Controller层

@Slf4j @RestController @RequestMapping("/report") public class ReportController { @Autowired private ReportService reportService; @GetMapping("/empJobData") public Result getEmpJobData(){ log.info("生成报表"); JobOption empJobData = reportService.getEmpJobData(); return Result.success(empJobData); } }
<!--统计每个性别的人数--> <select id="countEmpGenderData" resultType="java.util.Map"> selectif(gender = 1, '男', '女')as name, count(*) as value from emp group by gender ; </select>

if(条件判断,true_value,false_value)

数据转换过程

Java 对象Jackson 自动转换JSON 格式
List[ ]
Map{ }
map.put("pos", "班主任")"pos": "班主任"
map.put("total", 7)"total": 7

举例

java

// Java 中的 List<Map> [ {"pos": "班主任", "total": 7}, {"pos": "讲师", "total": 13} ]

Jackson 自动转成前端收到的 JSON:

json

[ { "pos": "班主任", "total": 7 }, { "pos": "讲师", "total": 13 } ]

总结

Spring Boot 默认集成了 Jackson,会自动把 Java 对象(List、Map、实体类)转成 JSON 格式返回给前端。你不需要写任何转换代码。

http://www.rkmt.cn/news/1496734.html

相关文章:

  • 【精品资料鉴赏】IPD与CBB研发技术管理体系
  • 广州医科大学考研辅导班正规机构,全维度榜单推荐 - 推荐评测师
  • 昇腾CANN Transformer算子库ops-transformer深度技术剖析:从FlashAttention内核到MoE稀疏计算的完整优化指南
  • 启点创新游乐场多商户分账管理系统,欢乐世界游乐园票务管理系统
  • 贵州纯玩包车避坑全解析:十大正规旅行社测评,贵阳美途说稳居榜首 - 美途说
  • ArchLinux Wayland 安裝Sway
  • 服务器推荐:从千卡智算集群到温水水冷,联想如何缩短大模型训练周期? - 资讯纵览
  • 武威市2026年黄金回收+白银回收+铂金回收+彩金回收品牌门店推荐及联系方式+地址+电话+靠谱店铺指南 - 盛世金银回收
  • [LC优选算法#2] 滑动窗口 | 长度最小的子数组 | 无重复字符的最长子串 | 最大连续1的个数
  • 深圳民办高中办学硬实力与口碑家长疑问解答 - 奔跑123
  • N_m3u8DL-RE:跨平台流媒体下载器的技术深度解析
  • 对外经济贸易大学考研辅导班正规机构,全维度榜单推荐 - 推荐评测师
  • 人工智能专业术语详解(E)
  • Java IO 流文件复制全解:字符缓冲流 vs 字节缓冲流
  • Java程序设计(第3版)第四章——继承的调用
  • 2026 三明厨卫屋面地下室漏水瓷砖空鼓测评:吉修匠 99.8 分五星榜首 - 吉修匠
  • 论文精读:喀斯特山地流域耕地流转的时空演变与地形梯度效应——以贵州南北盘江流域为例
  • HAMi 源码阅读笔记 01:HAMi调度简介
  • 金融行业常用哪些数据分析模型?风控、授信、客户分层框架汇总
  • 基础知识(从零开始学C语言)
  • Tcl语言:file命令的使用方式
  • 【MATLAB】基于模型预测控制的车辆圆轨迹跟踪方法研究
  • ngx_signal_worker_processes
  • 北京看守所律师事务所:驻所法律服务与常规代理有何本质区别? - 品牌2026
  • 丽水缙云县黄金回收指南:避开陷阱,多拿上千元 - 专业黄金回收
  • 细说KISS、YAGNI原则
  • 论文精读:基于GIS与地理探测器的西南喀斯特石漠化空间分布及驱动因子分析
  • 制造业领域:2026年值得关注的手推式/驾驶式/全自动工业扫地机制造商 - 企业推荐官【官方】
  • 2026义乌UV双喷服务机构整理推荐 - 奔跑123
  • 通诚无忧-通辽信息港信息平台运营策略:打造用户喜爱的通辽市本地服务社区