DAO层应抛出自定义受检异常,如UserNotFoundException继承DataAccessException,并携带原始异常和业务上下文(如ID),方法签名需显式声明throws,禁止吞异常或丢弃根因。

DAO 层抛出自定义异常,核心是让数据访问层的错误语义清晰、可追溯、不污染上层逻辑。不能直接 throw new Exception("查不到"),也不能把 SQLException 原样往上扔——前者丢失上下文,后者暴露技术细节且违反分层契约。
明确异常类型:用受检异常还是运行时异常?
DAO 层推荐统一使用自定义受检异常(继承 Exception),理由很实际:
- 强制调用方处理或声明,避免“静默失败”——比如查询用户不存在时,Service 层必须决定是返回空对象、抛业务异常,还是重试
- 与 JDBC/MyBatis 等框架常见受检异常(如
SQLException)风格一致,转换自然 - 便于在 Spring 等框架中做统一异常翻译(如
@ExceptionHandler或SQLExceptionTranslator)
设计合理的异常基类和子类
不要每个 DAO 方法都 throw 不同的异常类。建议分三级结构:
-
基类:如
DataAccessException extends Exception,只含通用构造器(带 message 和 cause) -
领域子类:如
UserNotFoundException extends DataAccessException、DuplicateUsernameException extends DataAccessException -
技术子类(可选):如
DatabaseConnectionException extends DataAccessException,用于封装底层连接失败等基础设施问题
这样 Service 层可以 catch DataAccessException 做统一封装,也可以对特定子类做差异化处理(如用户不存在时返回 404,重复用户名时返回 400)。
立即学习“Java免费学习笔记(深入)”;
抛出时必须携带原始根因和业务上下文
DAO 方法里捕获了 SQLException,不能丢掉它再 throw 新异常:
-
❌ 错误写法:
throw new UserNotFoundException("用户未找到")—— 根因丢失,无法区分是 SQL 语法错、表不存在,还是 WHERE 条件没匹配到数据 -
✅ 正确写法:
throw new UserNotFoundException("用户ID为 " + userId + " 未找到", sqlEx)—— 构造器传入 cause,堆栈里保留完整异常链
同时,在消息中加入关键变量(如 ID、SQL 片段、参数值),方便日志定位,但注意脱敏(不打密码、身份证号等)。
方法签名用 throws 显式声明,不吞异常
DAO 接口方法应明确声明可能抛出的异常类型,例如:
public User findById(Long id) throws UserNotFoundException, DatabaseConnectionException;
这有三个好处:
- 调用方一目了然知道该方法有哪些业务失败场景
- 编译器强制约束,避免遗漏处理
- 生成的 API 文档(如 Swagger)能自动体现错误码和含义
切忌在 DAO 实现里 try-catch 吞掉异常后 return null 或默认值——这会让上层误判为“查到了空结果”,而不是“查的过程出错了”。


















