员工管理-批量删除批量删除一是要利用foreach/foreach实现我们数组的sql语句拼接并且逻辑外键中我们不光要删除员工表的数据还要删除员工工作经历表的数据(都是根据员工id1.请求参数的接收既然是批量删除所以请求参数一般都是ids1,2,3...这种一般我们在Controller层拿数组或集合接既然谈到批量那么不然想到我们的xml配置文件中可以用循环foreach/foreach的方式2.Controller层-数组/集合封装参数方法一:用数组来接收如果前端参数名和形参数组名一样则无需RequestParam绑定方法二(推荐):用集合在来接收,无论如何都要加RequestParam绑定3.Service层注意逻辑外键的处理即可也就是员工表和员工经历表都是要协同着删除并且他们处于一个事务Transational当中要么全成功要么全失败。4.Mapper层根据原sql语句配置xmldelete from emp where id in (1,2,3....)xml配置如下(员工经历表类似)delete iddelete delete from emp where id in foreach collectionids itemid separator,open( close) #{id} /foreach /delete员工管理-修改1.查询回显:MyBatis 一对多查询使用 ResultMap 实现员工信息 工作经历封装1业务场景一个员工对应多条工作经历一对多关系。查询员工详细信息时需要将员工基本信息和关联的工作经历列表EmpExpr封装到一个Emp对象中。Emp对象中包含一个ListEmpExpr exprList属性EmpExpr是独立的工作经历实体外键emp_id指向emp.id2SQL 查询的问题sqlselect 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.company1张三10阿里1张三11腾讯但我们希望的Emp对象结构是json{ id: 1, name: 张三, exprList: [ { id: 10, company: 阿里 }, { id: 11, company: 腾讯 } ] }直接使用resultType无法自动完成这种一对多封装因此需要自定义resultMap。3解决方案自定义 ResultMap[1] Mapper 接口javaMapper public interface EmpMapper { Emp getById(Integer id); }[2] XML 映射文件核心xmlresultMap idempResultMap typecom.itheima.pojo.Emp!-- 主键必须用 id 标签 -- id columnid propertyid / !-- 普通字段用 result 标签 -- result columnusername propertyusername / result columnname propertyname / result columngender propertygender / result columnphone propertyphone / result columnjob propertyjob / result columnsalary propertysalary / result columnimage propertyimage / result columnentry_date propertyentryDate / result columndept_id propertydeptId / result columncreate_time propertycreateTime / result columnupdate_time propertyupdateTime / !-- 一对多封装集合属性 exprList --collection propertyexprList ofTypecom.itheima.pojo.EmpExprid columnee_id propertyid / result columnee_company propertycompany / result columnee_job propertyjob / result columnee_begin propertybegin / result columnee_end propertyend / result columnee_empid propertyempId / /collection /resultMap select idgetByIdresultMapempResultMap 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中的typexmlresultMap idempResultMap typecom.itheima.pojo.Emp表示最终要封装的 Java 对象类型是Emp。[2] idvsresult标签说明id映射数据库主键列MyBatis 会用它来区分不同对象result映射普通字段在一对多场景中id帮助 MyBatis 判断哪些行属于同一个主对象否则可能出现重复或死循环。[3]collection标签xmlcollection propertyexprList ofTypecom.itheima.pojo.EmpExpr属性说明propertyEmp 对象中存放集合的属性名ofType集合中每个元素的类型泛型其内部子标签和普通字段一样column是 SQL 返回的列名property是EmpExpr的属性名。[4] 为什么给工作经历字段起了别名sqlselect ee.id ee_id, ee.company ee_companyemp表和emp_expr表中都有id字段不取别名会冲突方便在resultMap中通过ee_id、ee_company区分来源(5)总结(resultType和resultMap)场景使用方式单表查询 / 字段名和属性名一致resultType 开启驼峰映射字段名和属性名不一致resultMap或Results一对多 / 一对一 / 集合嵌套必须使用resultMapcollection核心思想SQL 负责多表关联查出“扁平化”的数据resultMap负责将扁平数据“重组”为带有集合的层次化对象。(6)踩过的坑仅供参考忘记设置ofType→ 集合里是 Map不是 EmpExpr 对象id标签没有定义→ 集合可能重复或顺序错乱SQL 列名和resultMap的column不一致→ 值取不到属性为 null主对象字段也用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开发}]}对于正常员工表基本信息的修改不进行过多赘述主要强调修改员工工作经历信息则分为-先删再加1Controller层PutMapping public Result update(RequestBody Emp emp){ log.info(修改员工信息:{}, emp); empService.update(emp); return Result.success(); }RequestBody接受前端json格式数据2Service层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());---先删 ListEmpExpr exprList emp.getExprList(); if (!CollectionUtils.isEmpty(exprList)) { //要先给每个员工工作经历的empId因为前端传来的json数据是不包含empId的这里需要我们业务层自己处理exprList.forEach(empExpr - empExpr.setEmpId(emp.getId()));empExprMapper.insertBatch(exprList);--批量加入调用之前我们写过的mapper接口 } }3Mapper层update idupdate update empsetif testusername ! null and username ! username #{username}, /if if testpassword ! null and password ! password #{password}, /if if testname ! null and name ! name #{name}, /if if testgender ! null gender #{gender}, /if if testphone ! null and phone ! phone #{phone}, /if if testjob ! null job #{job}, /if if testsalary ! null salary #{salary}, /if if testimage ! null and image ! image #{image}, /if if testentryDate ! null entry_date #{entryDate}, /if if testdeptId ! null dept_id #{deptId}, /if if testcreateTime ! null create_time #{createTime}, /if /set where id #{id} /update我们在这里需要动态的根据传入进来的json数据来进行更新操作这里类似于条件查询所以我们需要引入where/where这个标签并且对于String类型不光要判断是否为null还需要判断是否为空字符串4wherevsset对比标签作用解决的问题where动态生成 WHERE 子句自动去掉第一个AND或ORset动态生成 SET 子句自动去掉最后一个多余的逗号但这个有个非常有意思的点就是where的确是可以根据情况删除的因为where属于sql语句中的可选条件但是set其实是DML语句中不可省略的即update 表名 set 更新内容 where 条件筛选解决办法这里有一种即在set里加一个“永远为 true 但无副作用”的条件我们的updateTime已经在Service层进行赋值了所以无论如何都可以在这里加上setupdate_time #{updateTime},if testusername ! 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 ResponseBodyExceptionHandler(异常类.class) 这个方法专门处理这种异常执行流程textController 抛出异常 ↓ 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是Duplicate1是entry2就是那个被单引号包着的重复值 // 取到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类封装数据/*** 员工职位人数统计*/DataNoArgsConstructorAllArgsConstructorpublic 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;搜索CASEselect 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层/** * 统计各个职位的员工人数 */ ListMapString,Object countEmpJobData()xml配置!-- 统计每个职位的数量-- !-- 统计各个职位的员工人数 -- select idcountEmpJobData resultTypejava.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为什么是ListMapString, ObjectListSQL 查询出来的是多行数据所以用List装每一行。Map每一行数据有多个列用Map的键值对来存——列名作 Key列值作 Value这里如果你的resultType是map集合则每一项的(Key,value)都是(列名,值)String列名是字符串比如pos、total所以 Key 的类型是String。Object列值可能是字符串职位名也可能是数字人数所以 Value 的类型用Object统一接收。一句话总结List管“行数”Map管“列数”String管“列名”Object管“列值”。这是一种通用的、灵活的数据结构专门用来接收那些没有对应 Java 实体类的查询结果比如统计报表。比如我们这项查询出来的ListMapString,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() { ListMapString,Object list empMapper.countEmpJobData(); ListObject jobList list.stream().map(dataMap - dataMap.get(pos)).toList(); ListObject 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 idcountEmpGenderData resultTypejava.util.Map selectif(gender 1, 男, 女)as name, count(*) as value from emp group by gender ; /selectif(条件判断true_valuefalse_value)数据转换过程Java 对象Jackson 自动转换JSON 格式List→[ ]Map→{ }map.put(pos, 班主任)→pos: 班主任map.put(total, 7)→total: 7举例java// Java 中的 ListMap [ {pos: 班主任, total: 7}, {pos: 讲师, total: 13} ]Jackson 自动转成前端收到的 JSONjson[ { pos: 班主任, total: 7 }, { pos: 讲师, total: 13 } ]总结Spring Boot 默认集成了 Jackson会自动把 Java 对象List、Map、实体类转成 JSON 格式返回给前端。你不需要写任何转换代码。