SpringBoot 学习第六天:全局异常处理 + 数据校验 + 事务管理 + 分布式锁基础 + 新面试题(无重复)
SpringBoot 学习第六天:全局异常处理 + 数据校验 + 事务管理 + 分布式锁基础 + 新面试题(无重复)
一、今日核心目标
-
掌握 SpringBoot 全局异常统一处理,实现标准化接口响应
-
精通 Validator 参数校验,完成入参合法性自动校验
-
深入 Spring 事务底层原理、传播机制、失效场景
-
理解分布式锁核心思想,掌握 Redis 分布式锁实现
-
掌握 Day6 专属高频面试题,与 Day1-Day5 无任何重复
二、全局异常统一处理(生产必备)
2.1 核心注解与实战代码
核心注解:
-
@RestControllerAdvice:全局异常切面,拦截所有 Controller 层抛出的异常 -
@ExceptionHandler:指定当前方法捕获的异常类型(可精准匹配单一异常或异常父类)
实战代码(可直接复制到项目中使用):
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j;
// 全局异常处理切面,结合SLF4J日志
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
// 捕获业务异常(自定义异常)
@ExceptionHandler(BusinessException.class)
public ResultVO handleBusinessException(BusinessException e) {
log.error("业务异常:{}", e.getMessage(), e);
return ResultVO.fail(e.getCode(), e.getMessage());
}
// 捕获参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO handleValidException(MethodArgumentNotValidException e) {
// 获取校验失败的提示信息
String msg = e.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
log.error("参数校验异常:{}", msg);
return ResultVO.fail(400, msg);
}
// 捕获全局异常(兜底)
@ExceptionHandler(Exception.class)
public ResultVO handleException(Exception e) {
log.error("系统异常:{}", e.getMessage(), e);
// 隐藏系统敏感信息,返回通用提示
return ResultVO.fail(500, "系统繁忙,请稍后再试");
}
}
// 自定义业务异常(可根据项目调整)
public class BusinessException extends RuntimeException {
private Integer code;
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
// getter/setter
public Integer getCode() {
return code;
}
}
// 统一响应结果(配合异常处理使用)
public class ResultVO {
private Integer code;
private String message;
private Object data;
// 成功/失败静态方法
public static ResultVO success(Object data) {
return new ResultVO(200, "操作成功", data);
}
public static ResultVO fail(Integer code, String message) {
return new ResultVO(code, message, null);
}
// 构造器、getter/setter
}
2.2 底层原理
SpringMVC 启动时,会自动扫描并注册带有 @RestControllerAdvice 注解的类作为「全局异常解析器」(实现 HandlerExceptionResolver 接口)。
当 Controller 方法执行过程中抛出异常时,SpringMVC 会通过「异常匹配器」找到对应 @ExceptionHandler 注解指定的异常类型,执行该方法并返回统一的 JSON 响应结果,避免代码中零散的 try/catch 冗余,同时隐藏系统敏感异常信息。
2.3 核心作用
-
统一接口异常返回格式,提升接口规范性,便于前端统一处理
-
集中记录异常日志,方便项目上线后定位问题
-
隐藏系统底层敏感信息(如数据库异常、代码报错详情),提升系统安全性
全局异常处理逻辑图:
常用校验注解(直接用在实体类字段上):
-
@NotBlank:字符串非空、非空白(不能是空格) -
@NotNull:任意类型不能为 null(适用于 Integer、Long 等包装类) -
@Email:字符串符合邮箱格式 -
@Length\(min = 6, max = 16\):字符串长度在指定范围 -
@Pattern\(regexp = \&\#34;^\\\[0\-9\]\{11\}$\&\#34;\):字符串匹配指定正则(如手机号) -
@Min\(1\):数字最小值(适用于 Integer、Long)
3.2 实战代码
// 1. 实体类(添加校验注解)
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Length;
public class UserDTO {
@NotBlank(message = "用户名不能为空")
@Length(min = 3, max = 10, message = "用户名长度3-10位")
private String username;
@NotBlank(message = "密码不能为空")
@Length(min = 6, max = 16, message = "密码长度6-16位")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
// getter/setter
}
// 2. Controller(添加@Valid注解触发校验)
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
public class UserController {
@PostMapping("/register")
public ResultVO register(@Valid @RequestBody UserDTO userDTO) {
// 校验通过,执行业务逻辑(无需手动判空)
return ResultVO.success("注册成功");
}
}
3.3 底层原理
基于 JSR-380 Java 校验规范,SpringBoot 自动集成 Hibernate Validator 作为具体实现。
当 Controller 方法添加 @Valid 注解后,Spring 会通过 AOP 前置拦截 机制,在目标方法执行前对入参进行校验:
-
校验参数是否符合注解规则,若不合法则抛出
MethodArgumentNotValidException异常 -
该异常会被全局异常处理器捕获,返回统一的校验失败提示
3.4 核心作用
-
替代手动判空、格式校验代码,减少冗余,提升开发效率
-
提前拦截非法参数,避免非法数据进入业务层、数据库,保证数据安全
-
统一校验提示,提升接口用户体验
Validator 参数校验逻辑图:
4.2 底层原理
Spring 事务的底层实现基于 AOP 动态代理(与 Day5 学习的 AOP 原理一致),结合 PlatformTransactionManager(事务管理器)实现:
-
当方法被
@Transactional标记时,Spring 会为该方法生成代理对象 -
代理对象执行方法前:通过事务管理器开启事务,获取数据库连接,设置事务隔离级别
-
执行目标方法:若方法正常执行,事务管理器提交事务,释放连接
-
若方法抛出异常:事务管理器回滚事务,释放连接(默认只回滚 RuntimeException 及其子类)
补充:SpringBoot 2.0 后默认使用 CGLIB 动态代理(无需目标类实现接口),可通过配置切换为 JDK 动态代理。
4.3 事务传播机制(重点)
通过 @Transactional\(propagation = Propagation\.XXX\) 设置,控制多个事务方法调用时的事务行为,常用 4 种:
-
REQUIRED(默认):如果当前有事务,就加入该事务;如果没有事务,就新建一个事务(最常用,如下单、支付场景)
-
REQUIRES_NEW:无论当前是否有事务,都新建一个独立事务,原事务挂起;新事务执行完成后,再恢复原事务(如日志记录,即使业务事务回滚,日志也要保留)
-
NESTED:嵌套事务,依赖于主事务;主事务回滚,子事务也会回滚;子事务回滚,主事务可选择不回滚(如订单下单后,扣减库存失败,仅回滚库存,不回滚订单)
-
SUPPORTS:支持当前事务,若当前没有事务,就以非事务方式运行(如查询接口,有无事务都可)
4.4 事务失效场景(高频踩坑点)
以下场景中,@Transactional 注解会失效,事务无法正常提交/回滚:
-
方法不是 public 修饰:Spring 事务仅对 public 方法生效(非 public 方法无法被 AOP 代理)
-
同类内部方法调用:如 service 中 methodA 调用本类的 methodB(methodB 加了 @Transactional),此时 methodB 的事务失效(因为内部调用不走代理)
-
异常被 try/catch 吃掉:方法内抛出异常后,手动 catch 但未重新抛出,事务无法感知异常,会正常提交
-
抛出非 RuntimeException 且未指定 rollbackFor:默认只回滚 RuntimeException 及其子类,若抛出 Checked 异常(如 IOException),需设置
@Transactional\(rollbackFor = Exception\.class\) -
多线程调用:两个事务方法分属不同线程,无法共享事务上下文,各自独立执行,无法回滚
-
数据库引擎不支持事务:如 MySQL 的 MyISAM 引擎(需切换为 InnoDB 引擎)
Spring 事务执行逻辑图:
5.4 核心注意事项
-
必须设置过期时间:防止服务突然宕机,锁无法释放,导致死锁
-
必须使用唯一值:避免线程 A 加的锁,被线程 B 误删除(如线程 A 超时未释放,线程 B 抢占锁后,线程 A 恢复执行删除锁)
-
释放锁必须用 Lua 脚本:保证“判断锁归属 + 删除锁”两步是原子操作,避免并发场景下的误删问题
-
可选优化:锁续期(如业务执行时间超过锁过期时间,通过定时任务延长锁的过期时间)
Redis 分布式锁执行逻辑图:

浙公网安备 33010602011771号