目錄
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
,方便后續的數據處理和展示。