文章目录
- 1.班级管理
-
- [1.1 班级列表查询 (分页查询)](#1.1 班级列表查询 (分页查询))
- [1.2 查询所有员工(显示班主任)](#1.2 查询所有员工(显示班主任))
- [1.3 新增班级](#1.3 新增班级)
- [1.4 根据ID查询班级(注意查询了才能修改)](#1.4 根据ID查询班级(注意查询了才能修改))
- [1.5 修改班级信息](#1.5 修改班级信息)
- [1.6 删除班级信息](#1.6 删除班级信息)
-
- [自定义异常 +全局异常处理器](#自定义异常 +全局异常处理器)
-
- [🧩 一、什么是"异常"(Exception)?](#🧩 一、什么是“异常”(Exception)?)
- [🧠 二、Java 为什么要有异常机制?](#🧠 二、Java 为什么要有异常机制?)
- [🧩 三、Java 中的异常类型(按继承关系)](#🧩 三、Java 中的异常类型(按继承关系))
- [🧩 四、为什么要自定义异常?](#🧩 四、为什么要自定义异常?)
- [🧩 五、自定义异常的好处](#🧩 五、自定义异常的好处)
- [🧩 六、异常处理的整体流程(项目中就是这样):](#🧩 六、异常处理的整体流程(项目中就是这样):)
- [✅ 七、一句话总结](#✅ 七、一句话总结)
1.班级管理
1.1 班级列表查询 (分页查询)
controller层
ClazzController.java
java
/**
* 分页查询班级
*/
@GetMapping
//这里是请求参数,用问号衔接
public Result getClazz(ClazzQueryParam clazzQueryParam) { //专门写一个封装类,把请求参数封装到里面
log.info("分页查询:{}", clazzQueryParam);
PageResult<Clazz> pageResult = clazzService.page(clazzQueryParam);
return Result.success(pageResult);
}
总结:
1.请求参数:这里根据接口文档可以看出,是一个请求参数(用?衔接),由于很长,所以可以专门写一个封装类,把请求参数封装到里面。如果不长,如果请求参数名与形参变量名相同,直接定义方法形参即可接收。(省略@RequestParam),不然可以用Spring提供的 @RequestParam 注解,将请求参数绑定给方法形参,如下:
java
@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer deptId){
System.out.println("根据ID删除部门: " + deptId);
return Result.success();
}
2.使用PageResult做返回结构,里面有总数据个数和对应页面的值
service层
ClazzServiceImpl.java
java
@Override
public PageResult<Clazz> page(ClazzQueryParam clazzQueryParam) {
PageHelper.startPage(clazzQueryParam.getPage(), clazzQueryParam.getPageSize());
List<Clazz> clazzList = clazzMapper.list(clazzQueryParam); //传进去的作用是后面要读取
if(!CollectionUtils.isEmpty(clazzList)){
LocalDate now = LocalDate.now();
for (Clazz clazz : clazzList) {
LocalDate BeginDate = clazz.getBeginDate();
LocalDate EndDate = clazz.getEndDate();
if(BeginDate.isAfter(now)){
clazz.setStatus("未开班");
}else if(now.isAfter(EndDate)){
clazz.setStatus("已结课");
}else{
clazz.setStatus("在读中");
}
}
}
Page<Clazz> p = (Page<Clazz>) clazzList;
return new PageResult<Clazz>(p.getTotal(), p.getResult());
}
总结:
1.处理一下satus,其实在sql中用case when处理也行。
2.这里要做的是封装为PageResult返回给controller层。pagehelper所做的事情就是:
(1)拦截你的查询,自动加上 LIMIT 和 OFFSET,实现分页。
(2)执行了另一个 COUNT 查询来获取总数。SELECT COUNT(*) FROM emp WHERE ...这样它就知道数据库总共有多少条满足条件的记录。
3.clazzList其实已经是一个Page对象了,里面已经有total这个值,只是声明为了list,如果直接声明为page会很奇怪,因为Mapper 方法本身并不真的返回 Page,是 PageHelper 在中间动的手脚,所以强转为page也是一种约定,这样最后用p.getTotal(), p.getResult()即可获得PageResult需要的两个值
4.需要返回多个内容的时候,可以创建list返回,mybatis会自动封装到list里面
mapper层
xml
<!--id:方法名 resultType:查询返回的单条记录所封装的类型,注意是单条!!-->
<select id="list" resultType = "org.example.pojo.Clazz">
select c.*, e.name as masterName from Clazz c left join emp e on c.master_id = e.id
<where>
<if test="name != null and name != ''">
c.name like concat('%', #{name}, '%')
</if>
<if test="begin != null and end != null">
and c.end_date between #{begin} and #{end}
</if>
</where>
</select>
总结:
1.实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装,开启驼峰命名映射的话也可以!!所以这里如果想把数据库返回的对应值封装到Clazz这个类中,只要开启驼峰命名,类似end_date会转为类中的endDate,可以封装成功。
1.2 查询所有员工(显示班主任)
1.3 新增班级
1.4 根据ID查询班级(注意查询了才能修改)
1.5 修改班级信息
1.6 删除班级信息
注意:在页面原型中,要求如果该班级下关联的有学生,是不允许删除的,并提示错误信息:"对不起, 该班级下有学生, 不能直接删除"。 (提示:可以通过自定义异常 + 全局异常处理器实现)
controller层
java
/**
* 删除班级
*/
@DeleteMapping("/{id}")//表示这里有个动态参数id
//路径参数 使用@PathVariable绑定给方法形参!!
public Result deleteClazz(@PathVariable Integer id) {
log.info("删除班级路径参数:{}", id);
clazzService.deleteClazzById(id);
return Result.success();
}
注意点:
1.这里是路径参数,也就是直接在/之后传入,需要用@PathVariable绑定给方法形参。并且DeleteMapping后面的路径("/{id}")要用大括号包裹起来,表示这是动态参数id。
Service层
ClazzService.java
java
void deleteClazzById(Integer id);
ClazzServiceImpl.java
java
/**
*根据id删除班级
*/
@Override
public void deleteClazzById(Integer id) {
int studentCount = studentMapper.countByClazzId(id);
if(studentCount > 0){
//先创建一个对象,再抛出,然后被全局异常处理器捕获
throw new DeletionNotAllowedException("对不起, 该班级下有学生, 不能直接删除");
}
clazzMapper.deleteClazzById(id);
}
Mapper层
ClazzMapper.java
java
void deleteClazzById(Integer id);
ClazzMapper.xml
xml
<!-- 删除班级-->
<delete id="deleteClazzById">
delete from clazz where id = #{id}
</delete>
StudentMapper.java
java
int countByClazzId(Integer id);
StudentMapper.xml
xml
<select id="countByClazzId" resultType ="Integer">
select count(*) from student where clazz_id = #{id}
</select>
注意事项:
一开始想的是select count(*) from student group by clazz_id having clazz_id = #{id} ,但是有点复杂了,其实不需要分组,因为已经知道id是多少了,直接用where计算就行。
另外having是对分组后的内容进行过滤,where是分组前。
一些关于分组查询的知识点复习:
注意事项:
- 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义【原因可以看上面,因为不知道要选谁】
- 执行顺序:where > 聚合函数 > having
where与having区别(面试题) - 执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组之后对结果进行过滤。
- 判断条件不同:where不能对聚合函数进行判断,而having可以。
自定义异常 +全局异常处理器
创建异常类
java
public class DeletionNotAllowedException extends RuntimeException {
public DeletionNotAllowedException(String message) {
//使得在抛出自定义异常时,可以带上自定义错误信息,并通过父类机制打印出来或传递给全局异常处理器。
super(message);
}
}
全局异常处理器
java
@ExceptionHandler(DeletionNotAllowedException.class)
public Result handleDeletionNotAllowedException(DeletionNotAllowedException e){
//把异常传进来处理
return Result.error(e.getMessage());
}
整个流程大概是:
程序捕获异常->创建异常对象->被全局处理器捕获->输出保存进去的msg
所以要做的就是,新建一个自定义异常,写到全局处理器里,在对应位置抛出该异常
🧩 一、什么是"异常"(Exception)?
异常(Exception)就是程序运行时发生的错误事件 ,
它打断了正常的程序流程。
简单说:
当代码遇到无法继续执行的情况时,JVM 就会抛出一个"异常对象",告诉你哪里出了问题。
🔧 举个例子:
java
int a = 10 / 0; // ❌ 除以零
运行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
这就叫"异常"。
它告诉我们:在运行时,出现了不能继续执行的情况。
🧠 二、Java 为什么要有异常机制?
因为错误是无法避免的 。
文件可能不存在、网络可能断开、数据可能为 null、输入可能错误......
如果没有异常机制,你就得在每一行代码都写 if...else 检查,非常麻烦。
Java 的异常机制帮你做了两件事:
- 检测错误:当程序出错时,JVM 自动创建异常对象;
- 处理错误 :你可以用
try...catch来捕获、处理错误,让程序不崩溃。
💡 例子:
java
try {
int result = 10 / 0; // 这里会出错
System.out.println("结果:" + result);
} catch (ArithmeticException e) {
System.out.println("出错了:" + e.getMessage());
}
输出:
出错了:/ by zero
✅ 程序不会崩溃,还能优雅地提示错误。
🧩 三、Java 中的异常类型(按继承关系)
Throwable
├── Error ← 程序无法恢复(JVM错误,比如内存溢出)
└── Exception
├── Checked Exception(受检异常)→ 编译器必须处理(比如IOException)
└── RuntimeException(运行时异常)→ 可选处理(比如NullPointerException)
📘 举几个常见的:
| 异常类型 | 示例 | 含义 |
|---|---|---|
NullPointerException |
对 null 调用了方法 | 空指针 |
ArithmeticException |
除以0 | 数学错误 |
ArrayIndexOutOfBoundsException |
访问数组越界 | 下标越界 |
IOException |
文件或网络出错 | I/O错误 |
SQLException |
SQL语句执行出错 | 数据库错误 |
🧩 四、为什么要自定义异常?
内置异常只能描述"技术错误",
比如"空指针"、"除0"、"SQL错误"。
但在真实业务中,你需要表达的是"业务逻辑错误"。
⚙️ 举个实际例子:
你的系统有一个"删除班级"的功能:
要求:如果班级下还有学生,就不允许删除。
用 Java 内置异常你只能抛 RuntimeException("不能删除")。
但这太笼统了,别人根本不知道是哪种删除逻辑出错。
于是我们写一个更语义化的异常:
java
public class DeletionNotAllowedException extends RuntimeException {
public DeletionNotAllowedException(String message) {
super(message);
}
}
然后使用它:
java
if (studentCount > 0) {
throw new DeletionNotAllowedException("对不起,该班级下有学生,不能直接删除");
}
这时别人一看日志:
org.example.exception.DeletionNotAllowedException: 对不起,该班级下有学生,不能直接删除
就一目了然:
👉 这是业务逻辑错误(禁止删除),不是程序崩溃。
🧩 五、自定义异常的好处
| 优点 | 说明 |
|---|---|
| ✅ 可读性高 | 一看类名就知道错误类型(如 DeletionNotAllowedException) |
| ✅ 可区分业务异常和系统异常 | 系统异常是代码错误,业务异常是逻辑错误 |
| ✅ 易于统一管理 | 可以在全局异常处理器中统一捕获并格式化返回给前端 |
| ✅ 更易调试 | 日志中清晰可见是哪种错误、在哪个模块触发的 |
🧩 六、异常处理的整体流程(项目中就是这样):
1️⃣ 程序运行中发现错误
↓
2️⃣ 抛出异常(throw)
↓
3️⃣ Spring 全局异常处理器捕获 (@ExceptionHandler)
↓
4️⃣ 封装成 Result 对象返回前端
↓
5️⃣ 前端显示错误提示
比如:
json
{
"code": 0,
"message": "对不起,该班级下有学生,不能直接删除"
}
✅ 七、一句话总结
异常 是程序在运行时报告错误的机制,
自定义异常 是为了让错误信息更符合业务语义,从而让系统更可读、更易维护、更优雅。
一些知识点:
路径参数(pathvariable),请求参数(@RequestParam),json格式的参数(requestbody)