目录
1.删除员工
1.1.1 需求
1.1.2 接口文档
1.1.3 思路分析
1.1.4 功能开发
1.1.4.1 Controller接收参数
1.1.4.2 Service
1.1.4.3 Mapper
1.1.5 功能测试
1.1.6 前后端联调
2.修改员工
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.5 前后端联调测试
3.异常处理
3.1 当前问题
3.2 解决方案
3.3 全局异常处理器
4.员工信息统计
4.1 职位统计
4.1.1 需求
4.1.2 接口文档
4.1.3 代码实现
4.2 性别统计
4.2.1 需求
4.2.2 接口文档
4.2.3 代码实现
1.删除员工
1.1.1 需求
勾选复选框后,实现批量删除员工。
1.1.2 接口文档
删除员工
-
基本信息
请求路径:
/emps
请求方式:
DELETE
接口描述:该接口用于批量删除员工的数据信息
-
请求参数
参数格式:查询参数
参数说明:
参数名 类型 示例 是否必须 备注 ids 数组 array 1,2,3 必须 员工的id数组 请求参数样例:
/emps?ids=1,2,3
- 响应数据
参数格式:application/json
参数说明:
参数名 | 类型 | 是否必须 | 备注 |
---|---|---|---|
code | number | 必须 | 响应码,1 代表成功,0 代表失败 |
msg | string | 非必须 | 提示信息 |
data | object | 非必须 | 返回的数据 |
响应数据样例:
{"code":1,"msg":"success","data":null
}
1.1.3 思路分析
1.1.4 功能开发
1.1.4.1 Controller接收参数
在 EmpController
中增加如下方法 delete
,来执行批量删除员工的操作。
方式一:在Controller方法中通过数组来接收
多个参数,默认可以将其封装到一个数组中,需要保证前端传递的参数名 与 方法形参名称保持一致。
/**
* 批量删除员工
*/
@DeleteMapping
public Result delete(Integer[] ids){log.info("批量删除部门: ids={} ", Arrays.asList(ids));return Result.success();
}
方式二:在Controller方法中通过集合来接收(推荐,因为集合操作元素会更加方便)
也可以将其封装到一个List<Integer> 集合中,如果要将其封装到一个集合中,需要在集合前面加上 @RequestParam
注解。
/**
* 批量删除员工
*/
@DeleteMapping
public Result delete(@RequestParam List<Integer> ids){log.info("批量删除部门: ids={} ", ids);empService.deleteByIds(ids);return Result.success();
}
1.1.4.2 Service
@Transactional //0.开启事务(涉及到多张表的删除)@Overridepublic void delete(List<Integer> ids) {//1.删除员工的基本信息数据 emp表empMapper.deleteBatch(ids);//2.删除员工的经历信息数据 emp_expr表empExprMapper.deleteBatch(ids);}
一定要加开启事务注解@Transactional,由于删除员工信息,包含删除基本信息和工作经历两部分,操作多次的删除,为了保证数据的一致性,所以要进行事务控制。
1.1.4.3 Mapper
1)EmpMapper
//错误写法: @Delete("delete from emp where id in #{ids}")// 必须基于xml书写动态SQL-foreach,因为参数是List集合类型void deleteBatch(List<Integer> ids);
<!--批量删除员工(1,2,3)--><delete id="deleteBatch">delete from emp where id in<foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach></delete>
代码解析如下:
-
<delete id="deleteBatch">
:定义了一个删除操作,id="deleteBatch"
表示这个操作的唯一标识,在 Java 代码中可以通过这个 id 来调用该删除操作。 -
delete from emp where id in
:这是 SQL 语句的一部分,表示要删除emp
表中满足id in
条件的数据。 -
<foreach>
标签:这是 MyBatis 提供的循环标签,用于遍历集合生成对应的 SQL 片段collection="ids"
:指定要遍历的集合名称为ids
(在 Java 代码中传入的参数名)item="id"
:表示集合中每个元素的别名separator=","
:指定元素之间的分隔符为逗号open="(" 和
close=")"
:表示遍历生成的内容会用括号包裹
当传入的ids
集合包含 1,2,3 时,这段代码会动态生成如下 SQL 语句:
delete from emp where id in (1, 2, 3)
这样就实现了根据多个 id 批量删除员工数据的功能,相比循环单条删除,这种方式更高效,减少了与数据库的交互次数。
2)EmpExprMapper
//基于xml开发-动态SQL--<foreach>--批量删除员工的经历void deleteBatch(List<Integer> empIds);
<!--批量删除员工经历--><delete id="deleteBatch">delete from emp_expr where emp_id in<foreach collection="empIds" item="empId" separator="," open="(" close=")">#{empId}</foreach></delete>
1.1.5 功能测试
1.1.6 前后端联调
2.修改员工
需要:修改员工信息
在进行修改员工信息的时候,我们首先先根据员工的ID查询员工的详细信息用于页面回显展示,然后用户修改员工数据之后,点击保存按钮,就可以将修改的数据提交到服务端,保存到数据库。具体操作为:
1.根据ID查询员工信息
2.保存修改的员工信息
2.1 查询回显
2.1.1 接口文档
根据ID查询员工数据
-
基本信息
请求路径:/emps/{id} 请求方式:GET 接口描述:该接口用于根据主键ID查询员工的信息
-
请求参数
参数格式:路径参数
参数说明:
参数名 类型 是否必须 备注 id number 必须 员工ID 请求参数样例:
/emps/1
-
响应数据
参数格式:application/json
参数说明:
名称 类型 是否必须 备注 code number 必须 响应码, 1 成功 , 0 失败 msg string 非必须 提示信息 data object 必须 返回的数据 |- id number 非必须 id |- username string 非必须 用户名 |- name string 非必须 姓名 |- password string 非必须 密码 |- entryDate string 非必须 入职日期 |- gender number 非必须 性别 , 1 男 ; 2 女 |- image string 非必须 图像 |- job number 非必须 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师 |- salary number 非必须 薪资 |- deptId number 非必须 部门id |- createTime string 非必须 创建时间 |- updateTime string 非必须 更新时间 |- exprList object[] 非必须 工作经历列表 |- id number 非必须 ID |- company string 非必须 所在公司 |- job string 非必须 职位 |- begin string 非必须 开始时间 |- end string 非必须 结束时间 |- empId number 非必须 员工ID
{"code": 1,"msg": "success","data": {"id": 2,"username": "zhangwuji","password": "123456","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,"createTime": "2022-09-01T23:06:30","updateTime": "2022-09-02T00:29:04","exprList": [{"id": 1,"begin": "2012-07-01","end": "2019-03-03""company": "百度科技股份有限公司""job": "java开发","empId": 2},{"id": 2,"begin": "2019-3-15","end": "2023-03-01""company": "阿里巴巴科技股份有限公司""job": "架构师","empId": 2}]}
}
2.1.2 实现思路
在查询回显时,既需要查询出员工的基本信息,又需要查询出该员工的工作经历信息。
/*** 员工回显* @param id* @return*/@GetMapping("/emps/{id}")public Result getById(@PathVariable Integer id){log.info("参数回显员工,id = {}", id);Emp emp = empService.getById(id);return Result.success(emp);}
我们可以先通过一条SQL语句,查询出指定员工的基本信息,及其员工的工作经历信息。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 代码实现
1). EmpController
添加 getById
用来根据ID查询员工数据,用于页面回显
/*** 员工回显* @param id* @return*/@GetMapping("/emps/{id}")public Result getById(@PathVariable Integer id){log.info("参数回显员工,id = {}", id);Emp emp = empService.getById(id);return Result.success(emp);}
2). EmpServiceImpl
接口中增加 getById
方法
@Overridepublic Emp getById(Integer id) {//1.调用mapper查询方法,获取员工基本信息以及经历列表信息return empMapper.getById(id);}
3). EmpMapper
接口中增加 getById
方法
/*** 根据员工id查询员工基本信息以及经历列表信息* @param id* @return*///@Select("select * from emp e left join emp_expr ee on e.id = ee.emp_id where e.id = #{id};")Emp getById(Integer id);
4).EmpMapper.xml
配置文件中定义对应的SQL
<!--resultMap标签,进行一对多数据映射,autoMapping设置为true可以进行自动映射--><resultMap id="empResultMap" type="com.itheima.entity.Emp" autoMapping="true"><!--id标签,主键映射--><id property="id" column="id"></id><!--collection标签:用来封装集合数据,适用于一对多的情况--><collection property="exprList" ofType="com.itheima.entity.EmpExpr"><id column="ee_id" property="id"></id><result column="ee_empId" property="empId"></result><result column="ee_begin" property="begin"></result><result column="ee_end" property="end"></result><result column="ee_company" property="company"></result><result column="ee_job" property="job"></result></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.job ee_job,ee.company ee_companyfrom emp eleft join emp_expr ee on e.id = ee.emp_idwhere e.id = #{id}</select>
在这种一对多的查询中,我们要想成功的封装的结果,需要手动的基于 <resultMap>
来进行封装结果。
2.1.4 方式二
实现员工信息回显的第二种方式:在Service层调用两次Mapper层的查询方法,分别查询员工的基本信息和工作经历列表
Service层:
@Overridepublic Emp getById(Integer id) {//方式一://调用mapper查询方法,获取员工基本信息以及经历列表信息//return empMapper.getById(id);//方式二//1.查询员工基本信息,封装到Emp对象中Emp emp = empMapper.getById2(id);//2.查询员工经历列表信息,封装到Emp对象中List<EmpExpr> empExprList = empExprMapper.getByEmpId(id);emp.setExprList(empExprList);//3.返回员工Emp对象return emp;}
EmpMapper:
/*** 根据员工id查询员工基本信息以及经历列表信息* @param id* @return*///@Select("select * from emp e left join emp_expr ee on e.id = ee.emp_id where e.id = 44;")Emp getById(Integer id);@Select("select * from emp where id = #{id}")Emp getById2(Integer id);
单纯的查询员工的基本信息可以不用xml,直接用注解的方式实现就好了
EmpExprMapper:
//基于xml开发-动态SQL--<foreach>--批量查询员工经历List<EmpExpr> getByEmpId(Integer empId);
<!--根据id查询员工所有经历--><select id="getByEmpId" resultType="com.itheima.entity.EmpExpr">select * from emp_expr where emp_id = #{id}</select>
也可以使用简单的注解方式。
对于单纯的查询操作,不需要使用事务管理。
原因如下:
查询操作是只读的:事务主要用于保证数据的一致性和完整性,主要在修改、插入、删除等写操作中发挥作用。
MyBatis默认就是非事务查询:查询操作默认在自动提交模式下执行,每条SQL语句执行后立即生效。
当前查询逻辑简单:先查询员工基本信息,再查询员工工作经历,最后组合返回
即使其中一个查询失败,也不会对数据造成不一致的影响
什么时候需要事务?
1.多个写操作需要保持一致性:比如你代码中的保存员工信息方法。2.多个操作需要原子性执行:要么全部成功,要么全部失败。
总结
对于你目前的查询员工基本信息和工作经历的需求,不需要添加事务管理。查询操作本身就是安全的,即使在极少数情况下第二个查询失败,也不会对数据库造成任何影响,最多只是返回不完整的数据,这可以通过其他方式(如异常处理)来解决。
只有在进行数据修改操作时,才需要考虑使用事务来保证数据的一致性。
Mybatis中封装数据查询结果,什么时候用resultType,什么时候用resultMap?
1. resultType
:简单直接的 “自动匹配”
适用情况:当数据库查询结果的字段名,和你定义的实体类(比如 Java 的 User
类)的属性名完全一样时,用 resultType
。
举个例子:
- 数据库表
user
有字段:id
、name
、age
- 你的实体类
User
有属性:id
、name
、age
(变量名和字段名完全相同)
这时候在 MyBatis 的 SQL 映射文件里,直接写:
<select id="getUser" resultType="com.example.User">select id, name, age from user where id = #{id}
</select>
MyBatis 会自动把查询到的 id
对应到 User
的 id
属性,name
对应 name
属性,根本不用你操心 —— 这就是 “自动匹配”。
2. resultMap
:需要手动 “牵线搭桥”
适用情况:当数据库字段名和实体类属性名不一样,或者实体类里有复杂属性(比如关联了另一个对象)时,必须用 resultMap
手动定义对应关系。
情况 1:字段名和属性名不一样
- 数据库表
user
有字段:user_id
、user_name
(字段名带前缀) - 实体类
User
有属性:id
、name
(属性名没有前缀)
这时候字段名和属性名对不上,MyBatis 不知道 user_id
该放到 id
里,所以需要用 resultMap
手动指定:
<!-- 先定义一个resultMap,告诉MyBatis字段和属性的对应关系 -->
<resultMap id="userMap" type="com.example.User"><id column="user_id" property="id"/> <!-- 数据库字段user_id对应实体类的id --><result column="user_name" property="name"/> <!-- 数据库字段user_name对应实体类的name -->
</resultMap><!-- 在查询时使用这个resultMap -->
<select id="getUser" resultMap="userMap">select user_id, user_name from user where user_id = #{id}
</select>
情况 2:实体类有复杂属性(关联对象)
比如 User
类里不仅有基本信息,还有一个 Dept
类型的属性(表示用户所属部门):
public class User {private int id;private String name;private Dept dept; // 关联的部门对象(复杂属性)
}public class Dept {private int deptId;private String deptName;
}
数据库查询可能同时查出用户和部门信息(比如 user.id
、user.name
、dept.dept_id
、dept.dept_name
),这时候需要用 resultMap
告诉 MyBatis 如何把部门信息装进 User
的 dept
属性里:
<resultMap id="userWithDeptMap" type="com.example.User"><id column="id" property="id"/><result column="name" property="name"/><!-- 关联Dept对象,用association标签 --><association property="dept" javaType="com.example.Dept"><id column="dept_id" property="deptId"/><result column="dept_name" property="deptName"/></association>
</resultMap><select id="getUserWithDept" resultMap="userWithDeptMap">select u.id, u.name, d.dept_id, d.dept_name from user u left join dept d on u.dept_id = d.dept_id where u.id = #{id}
</select>
总结
resultType
:简单场景用,字段名和属性名完全一致时,MyBatis 自动帮你 “装数据”。resultMap
:复杂场景用,当字段名和属性名不一样,或者有关联对象、集合等复杂属性时,需要你手动定义 “对应规则”,告诉 MyBatis 如何 “装数据”。
简单说就是:能自动对应就用 resultType
,不能自动对应就用 resultMap
手动配置。
2.2 修改员工
查询回显之后,就可以在页面上修改员工的信息了。
当用户修改完数据之后,点击保存按钮,就需要将数据提交到服务端,然后服务端需要将修改后的数据更新到数据库中 。
而此次更新的时候,既需要更新员工的基本信息; 又需要更新员工的工作经历信息 。
2.2.1 接口文档
-
基本信息
请求路径:/emps 请求方式:PUT 接口描述:该接口用于修改员工的数据信息
-
请求参数
参数格式:application/json
参数说明:
名称 类型 是否必须 备注 id number 必须 id username string 必须 用户名 name string 必须 姓名 gender number 必须 性别, 说明: 1 男, 2 女 image string 非必须 图像 deptId number 非必须 部门id entryDate string 非必须 入职日期 job number 非必须 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师 salary number 非必须 薪资 exprList object[] 非必须 工作经历列表 |- id number 非必须 ID |- company string 非必须 所在公司 |- job string 非必须 职位 |- begin string 非必须 开始时间 |- end string 非必须 结束时间 |- empId number 非必须 员工ID 请求数据样例:
{"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": [{"id": 1,"begin": "2012-07-01","end": "2015-06-20""company": "中软国际股份有限公司""job": "java开发","empId": 2},{"id": 2,"begin": "2015-07-01","end": "2019-03-03""company": "百度科技股份有限公司""job": "java开发","empId": 2}]}
- 响应数据
参数格式:application/json
参数说明:
参数名 | 类型 | 是否必须 | 备注 |
---|---|---|---|
code | number | 必须 | 响应码,1 代表成功,0 代表失败 |
msg | string | 非必须 | 提示信息 |
data | object | 非必须 | 返回的数据 |
响应数据样例:
{"code":1,"msg":"success","data":null
}
2.2.2 实现思路
2.2.3 代码实现
1)Controller层:
/*** 修改员工* @param emp* @return*/@PutMappingpublic Result update(@RequestBody Emp emp){log.info("修改员工:emp={}",emp);empService.update(emp);return Result.success();}
2)EmpServiceImpl层实现类实现update方法
@Transactional //开启事务@Overridepublic void update(Emp emp) {//1.修改员工的基本信息 -- emp//1.1 补充基础属性--更新时间emp.setUpdateTime(LocalDateTime.now());empMapper.update(emp);//2.修改员工的工作经历信息 -- emp_expr//先删后增empExprMapper.deleteByEmpId(emp.getId());List<EmpExpr> exprList = emp.getExprList();if (!CollectionUtils.isEmpty(exprList)) {//关联员工idexprList.forEach(expr -> {expr.setEmpId(emp.getId());});empExprMapper.insertBatch(exprList);}}
修改员工的工作经历时,我们需要增删改,可以分开调用三个Mapper方法。但是更简便的方法是先删除后增加,在前端页面选择员工后,将id传到后端,接收后先在后端数据库中将此员工对应的经历信息全部删除,删除之后我们再用emp.getExprList()方法将前端传入服务器的员工经历信息重新insert到emp_expr表中,但是我们能直接添加吗?我们在insert前需要做两件事:
- 关联员工id
如果直接添加,数据库中并没有emp_id数据,这些直接添加的数据会变为脏数据,从而没有对应的员工,所以为了避免这个问题我们需要用foreach遍历exprList集合将每一条员工经历信息都与当前emp中的id对应起来(注意这里是员工经历表emp_expr中的emp_id对应emp表中的id)。
- 判断非空
如果前端页面当前员工本来就没有员工数据,可是我们依旧执行了insert操作,此时就会报错,为了解决这个问题,我们可以在关联员工id以及insert操作前进行判断,如果expList集合为非空,再进入进行操作。
3)EmpMapper接口中增加update方法
/*** 更新员工--动态SQL* @param emp*///基于xml开发动态SQLvoid update(Emp emp);
4)EmpMapper.xml
配置文件中定义对应的SQL语句,基于动态SQL更新员工信息
<!--根据ID更新员工信息--><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>
1. 动态更新(部分更新)
这种方式支持动态更新,即只更新用户提供的字段,而不是更新所有字段。这样做的好处包括:
- 避免覆盖未提供字段的值:如果某个字段没有在请求中提供,不会将其更新为null或空值
- 提高性能:只更新需要修改的字段,减少不必要的数据库操作
- 更灵活:前端可以只传递需要修改的字段,而不必传递所有字段
2. 防止SQL语法错误
<set>标签会自动处理SQL语句中的逗号问题:
- 自动添加SET关键字
- 自动去除最后一个更新字段后的逗号
- 如果没有任何字段需要更新,会避免语法错误
3. 安全性
使用<if>标签进行条件判断,确保只有在字段不为空的情况下才进行更新,避免将null或空字符串更新到数据库中。
这里是如何实现只更新前端修改了的字段呢?
1.前端只会传入需要更新的字段,而其余字段为null,eg:
前端只更新员工的用户名和电话号码:
{"id": 10,"username": "newUser123","phone": "13800138000"
}
2.springboot会自动将json格式的请求数据转换为Emp对象:
Emp emp = new Emp();
emp.setId(10); // 有值:10
emp.setUsername("newUser123"); // 有值:"newUser123"
emp.setPhone("13800138000"); // 有值:"13800138000"
// 其他字段都没有传入,所以都是null
// emp.getPassword() == null
// emp.getName() == null
// emp.getGender() == null
// ...
3. MyBatis处理过程
XML中的每个<if>判断都会检查对应字段:
<!-- username有值,条件满足,会生成这部分SQL -->
<if test="username != null and username != ''">username = #{username},</if><!-- password是null,条件不满足,不会生成SQL -->
<if test="password != null and password != ''">password = #{password},</if><!-- name是null,条件不满足,不会生成SQL -->
<if test="name != null and name != ''">name = #{name},</if><!-- phone有值,条件满足,会生成这部分SQL -->
<if test="phone != null and phone != ''">phone = #{phone},</if>
4. 最终生成的SQL
UPDATE emp SET username = 'newUser123', phone = '13800138000' WHERE id = 10
数据库中其他字段保持原值不变,只有username和phone被更新。
2.2.5 前后端联调测试
点击保存之后,查看更新后的数据。
3.异常处理
3.1 当前问题
当我们在修改部门数据的时候,如果输入一个在数据库表中已经存在的手机号,点击保存按钮之后,前端提示了错误信息,但是返回的结果并不是统一的响应结果,而是框架默认返回的错误结果
状态码为500,表示服务器端异常,我们打开idea,来看一下,服务器端出了什么问题。
上述错误信息的含义是,emp
员工表的phone
手机号字段的值重复了,因为在数据库表emp
中已经有了13309090001这个手机号了,我们之前设计这张表时,为phone
字段建议了唯一约束,所以该字段的值是不能重复的。
而当我们再将该员工的手机号也设置为 13309090001
,就违反了唯一约束,此时就会报错。
我们来看一下出现异常之后,最终服务端给前端响应回来的数据长什么样。
响应回来的数据是一个JSON格式的数据。但这种JSON格式的数据还是我们开发规范当中所提到的统一响应结果Result吗?显然并不是。由于返回的数据不符合开发规范,所以前端并不能解析出响应的JSON数据 。
接下来我们需要思考的是出现异常之后,当前案例项目的异常是怎么处理的?
- 答案:没有做任何的异常处理
当我们没有做任何的异常处理时,我们三层架构处理异常的方案:
-
Mapper接口在操作数据库的时候出错了,此时异常会往上抛(谁调用Mapper就抛给谁),会抛给service。
-
service 中也存在异常了,会抛给controller。
-
而在controller当中,我们也没有做任何的异常处理,所以最终异常会再往上抛。最终抛给框架之后,框架就会返回一个JSON格式的数据,里面封装的就是错误的信息,但是框架返回的JSON格式的数据并不符合我们的开发规范。
3.2 解决方案
那么在三层构架项目中,出现了异常,该如何处理?
-
方案一:在所有Controller的所有方法中进行try…catch处理
-
缺点:代码臃肿(不推荐)
-
-
方案二:全局异常处理器
-
好处:简单、优雅(推荐)
-
3.3 全局异常处理器
我们该怎么样定义全局异常处理器?
-
定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。
-
在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。
@Slf4j
@RestControllerAdvice //作用:用来捕获控制器controller层抛出的异常
//@ControllerAdvice
//@ResponseBody
public class GlobalExceptionHandler {@ExceptionHandler //指定处理何种异常,默认处理所有类型异常public Result doException(Exception ex){log.error(ex.getMessage());return Result.error("出错了,请联系管理员!");}
}
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
处理异常的方法返回值会转换为json后再响应给前端
重新启动SpringBoot服务,打开浏览器,再来测试一下 修改员工 这个操作,我们依然设置已存在的 "13309090001" 这个手机号:
此时,我们可以看到,出现异常之后,异常已经被全局异常处理器捕获了。然后返回的错误信息,被前端程序正常解析,然后提示出了对应的错误提示信息。
以上就是全局异常处理器的使用,主要涉及到两个注解:
@RestControllerAdvice //表示当前类为全局异常处理器
@ExceptionHandler //指定可以捕获哪种类型的异常进行处理
4.员工信息统计
员工管理的增删改查功能我们已经全部实现了,接下来我们再来完成员工信息统计的接口开发。对于这些图形报表的开发,其实都是基于现成的一些图形报表的组件开发的,比如:Echarts、HighCharts等。
而报表的制作,主要是前端人员开发,引入对应的组件(比如:ECharts)即可。 服务端开发人员仅为其提供数据即可。
官网:Apache ECharts
4.1 职位统计
4.1.1 需求
对于这类的图形报表,服务端要做的,就是为其提供数据即可。 我们可以通过官方的示例,看到提供的数据其实就是X轴展示的信息,和对应的数据。
4.1.2 接口文档
1). 基本信息
请求路径:/report/empJobData
请求方式:GET
接口描述:统计各个职位的员工人数
2). 请求参数
无
3). 响应数据
参数格式:application/json
参数说明:
参数名 | 类型 | 是否必须 | 备注 |
---|---|---|---|
code | number | 必须 | 响应码,1 代表成功,0 代表失败 |
msg | string | 非必须 | 提示信息 |
data | object | 非必须 | 返回的数据 |
|- jobList | string[] | 必须 | 职位列表 |
|- dataList | number[] | 必须 | 人数列表 |
响应数据样例:
{"code": 1,"msg": "success","data": {"jobList": ["教研主管","学工主管","其他","班主任","咨询师","讲师"],"dataList": [1,1,2,6,8,13]}
}
为了封装上面需要给前端返回的数据,在entity包下再创建一个实体类 JobOption
,封装给前端返回的结果:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JobOption {private List jobList; //职位列表private List dataList; //数据列表}
其实返回给前端的json数据就是两条json格式的数据,一条是树状图x轴的职位名称,一条是y轴的人数,我们可以将这两条数据存到两个集合里然后封装到JobOption对象中返回给前端。
4.1.3 代码实现
1). 定义ReportController,并添加方法。
@Slf4j
@RequestMapping("/report")
@RestController
public class ReportController {@Autowiredprivate ReportService reportService;/*** 员工职位数据统计* @return*/@GetMapping("/empJobData")public Result getEmpJobData() {log.info("开始处理员工职位数据统计");JobOption jobOption = reportService.getEmpJobData();return Result.success(jobOption);}
}
将职位列表和数据列表都封装到JobOption类型的对象中,要通过调用ReportServiceImpl层的getEmpJobData()方法。
2). 定义ReportServiceImpl实现类,并实现方法
@Service
public class ReportServiceImpl implements ReportService {@Autowiredprivate EmpMapper empMapper;@Overridepublic JobOption getEmpJobData() {//1.调用mapper接口,获取统计数据List<Map<String, Object>> list = empMapper.countEmpJobData(); //map: 职位=校验主管,人数=1//2.组装结果,并返回List<Object> jobList = list.stream().map(dataMap -> dataMap.get("职位")).toList();List<Object> dataList = list.stream().map(dataMap -> dataMap.get("人数")).toList();return new JobOption(jobList, dataList);}
}
在Java中,List<Map<String, Object>> 这种类型定义中,Map<String, Object> 的键和值的类型是必须指定的。这是因为Java是一门强类型语言,需要在编译时确定泛型类型。
让我来解释一下这个类型定义:
List<Map<String, Object>> 表示一个列表,列表中的每个元素都是一个 Map<String, Object> 类型的对象
Map<String, Object> 表示一个映射表,其中:
- 键(key)的类型是 String
- 值(value)的类型是 Object,这意味着值可以是任何对象类型
虽然键的类型固定为 String,但值的类型是 Object,这在Java中是一个通用的父类,可以接受任何类型的对象。所以值可以是各种不同的类型,比如 String、Integer、Double 等
此方法会调用empMapper层的方法,得到List<Map<String,Object>>类型的Map集合list,接着我们要根据Map集合的Key获取到里面对应的value值,我们可以基于stream流来操作,我们要拿到Map集合,并对Map集合中的数据进行处理,然后封装到新的list中,我们就要使用map()方法。遍历Map集合中职位所对应的每一条value值,然后用toList()封装到新的集合当中。
3). 定义EmpMapper 接口
统计的是员工的信息,所以需要操作的是员工表。 所以代码我们就写在 EmpMapper
接口中即可。
/*** 统计员工职位数据* @return*/
@MapKey("职位")
List<Map<String,Object>> countEmpJobData();
如果查询的记录往Map中封装,可以通过@MapKey注解指定返回的map中的唯一标识是那个字段。【也可以不指定】
4). 定义EmpMapper.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 joborder by total
</select>
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.2 性别统计
4.2.1 需求
对于这类的图形报表,服务端要做的,就是为其提供数据即可。 我们可以通过官方的示例,看到提供的数据就是一个json格式的数据。
4.2.2 接口文档
1). 基本信息
请求路径:/report/empGenderData
请求方式:GET
接口描述:统计员工性别信息
2). 请求参数
无
3). 响应数据
参数格式:application/json
参数说明:
参数名 | 类型 | 是否必须 | 备注 |
---|---|---|---|
code | number | 必须 | 响应码,1 代表成功,0 代表失败 |
msg | string | 非必须 | 提示信息 |
data | object[] | 非必须 | 返回的数据 |
|- name | string | 非必须 | 性别 |
|- value | number | 非必须 | 人数 |
响应数据样例:
{"code": 1,"msg": "success","data": [{"name": "男性","value": 5},{"name": "女性","value": 6}]
}
4.2.3 代码实现
1). 在ReportController,添加方法。
/**
* 员工性别数据统计
* @return
*/
@GetMapping("/empGenderData")
public Result getEmpGenderData() {log.info("开始处理员工性别数据统计");List<Map<String,Object>> genderList = reportService.getEmpGenderData();return Result.success(genderList);
}
2). 在ReportService接口,添加接口方法。
/*** 统计员工性别信息*/
List<Map<String,Object>> getEmpGenderData();
3). 在ReportServiceImpl实现类,实现方法
/*** 获取员工性别数据** @return*/@Overridepublic List<Map> getEmpGenderData() {return empMapper.countEmpGenderData();}
4). 定义EmpMapper 接口
/*** 统计员工性别数据* @return*/@MapKey("name")List<Map<String,Object>> countEmpGenderData();
5). 定义EmpMapper.xml
<!--查询员工性别数据统计--><select id="countEmpGenderData">selectif(gender = 1, '男性员工', '女性员工') name,count(*) valuefrom empgroup by gender;</select>
1. SQL 中的 IF
函数
-
作用:
IF
函数用于实现条件判断,根据指定的条件返回不同的值。 -
语法:
IF(condition, value_if_true, value_if_false)
。 -
示例:在
EmpMapper.xml
里的 SQL 语句IF(gender = 1, '男', '女') as name
中,condition
是gender = 1
,当员工的gender
字段值为1
时,返回'男'
;当gender
字段值不为1
时,返回'女'
。这样就可以根据gender
的值,将其转换为对应的中文性别名称,方便后续展示等操作。
2. SQL 中的 IFNULL
函数
-
作用:
IFNULL
函数用于判断第一个表达式是否为NULL
,如果是,就返回第二个参数的值;如果不是,就返回第一个参数的值。 -
语法:
IFNULL(expr1, expr2)
。 -
示例:比如在查询员工工资时,有些员工工资可能为
NULL
,我们希望将其显示为0
,可以这样写 SQL 语句:SELECT IFNULL(salary, 0) as salary FROM emp
。这里expr1
是salary
字段,如果salary
为NULL
,就返回0
;否则返回salary
本身的值。这样可以保证查询结果中工资字段不会出现NULL
,方便后续的数据处理和展示。