Mapper层不建议直接抛业务异常,应由Service层调用Mapper前后检查结果并抛出自定义异常,如查询为空时throw UserDataNotFoundException,配合@ControllerAdvice统一处理。

在 Java 的 MyBatis(或 MyBatis-Plus)项目中,Mapper 层本身不建议直接抛出业务异常(包括自定义数据异常),因为 Mapper 接口是 MyBatis 生成代理对象的契约,其方法默认只声明 throws RuntimeException(运行时异常),且底层 SQL 执行失败会自动包装为 org.apache.ibatis.exceptions.PersistenceException 等运行时异常。
但你确实可以「在 Mapper 层语义上」实现对数据异常的识别与主动抛出——关键在于:把校验和异常抛出逻辑放在 Service 层调用 Mapper 前后,或借助 Mapper 方法返回结果后的判断 + 显式 throw。下面分几种常用、合理的方式说明:
✅ 方式一:Service 层调用 Mapper 后检查结果并抛出自定义异常
这是最推荐、最清晰的做法。Mapper 只负责查/改/删,不处理业务逻辑;Service 拿到结果后判断是否符合业务预期,再决定是否抛异常。
- 例如查询用户,但数据库没查到:
// UserMapper.java User selectById(Long id);
// UserService.java
public User getUserById(Long id) {
User user = userMapper.selectById(id);
if (user == null) {
throw new UserDataNotFoundException("用户不存在,id=" + id);
}
return user;
}
- 优点:职责分离明确,异常语义清晰,便于统一异常处理(如 @ControllerAdvice)
- 注意:不要在 Mapper 接口方法上声明
throws UserDataNotFoundException(编译会报错,因 MyBatis 代理不支持受检异常)
✅ 方式二:用 Optional 包装返回值(语义增强,仍需 Service 判空)
虽不能改变 Mapper 的异常机制,但可提升可读性:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
// Mapper 接口保持不变(MyBatis 不支持 Optional 作为返回类型直接映射)
// ✅ 正确做法:在 Service 中封装
public Optional<User> findUserOptById(Long id) {
return Optional.ofNullable(userMapper.selectById(id));
}
后续使用:findUserOptById(100L).orElseThrow(() -> new UserDataNotFoundException("用户未找到"));
✅ 方式三:在 XML 或注解 SQL 中用 <if> / @SelectProvider 做前置校验(不推荐用于异常抛出)
SQL 层适合做数据过滤,不适合抛 Java 异常。MyBatis 无法在 SQL 执行过程中 throw 自定义异常。你可以用 <where> 动态拼条件,但“抛异常”必须交由 Java 层完成。
- 错误示例(语法不通,MyBatis 不解析 Java throw):
<!-- ❌ 无效!MyBatis XML 不支持 Java throw 语句 -->
<if test="id == null">throw new UserDataException("id 不能为空");</if>
✅ 补充:全局异常统一处理(让自定义异常生效)
定义好异常后,配合 Spring 的 @ControllerAdvice 统一捕获,返回友好 JSON:
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserDataNotFoundException extends RuntimeException {
public UserDataNotFoundException(String msg) {
super(msg);
}
}
<p>// 全局处理器
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserDataNotFoundException.class)
@ResponseBody
public Result<?> handleUserDataNotFound(UserDataNotFoundException e) {
return Result.fail(404, e.getMessage());
}
}
不复杂但容易忽略:Mapper 是数据访问契约,不是业务入口。所有数据合法性判断、空值处理、异常语义包装,都应该落在 Service 层 —— 这样既符合分层规范,又利于测试、复用和维护。

















