核心目标是屏蔽技术细节、保留根因线索、支持业务语义表达、便于日志追踪和前端反馈;需继承runtimeexception、提供带cause构造函数、消息面向业务、日志完整输出堆栈、统一处理器返回标准json。

数据库操作失败时,不直接抛出 SQLException 或其子类,而是用自定义异常封装,核心目标是:对上层屏蔽技术细节、保留根因线索、支持业务语义表达、便于日志追踪和前端反馈。
定义轻量级数据访问异常基类
继承 RuntimeException,避免强制声明 throws,同时提供带 cause 的构造函数:
- 必须有
public DataAccessException(String message, Throwable cause)—— 否则原始堆栈在构造时被截断 - 可选添加
public DataAccessException(ErrorCode code, String message, Throwable cause),把错误码与消息解耦 - 不建议继承
Exception(受检异常),否则 Service 层需处处 throws,违背分层抽象原则
在 DAO 或 Repository 层捕获并封装
底层 JDBC、MyBatis 或 JPA 抛出异常后,立即转为自定义异常,消息面向业务而非驱动:
- ✅ 正确:
throw new DataAccessException("订单查询失败,请稍后重试", e) - ❌ 错误:
throw new DataAccessException("com.mysql.cj.jdbc.exceptions.CommunicationsException: Connection refused", e)—— 暴露驱动类名和环境细节 - 若需区分场景(如连接失败 vs 查询超时),可用子类如
DbConnectionException或DbTimeoutException,但无需为每个 SQL 错误码单独建类
配合日志与全局异常处理器
封装不是终点,要让信息真正可用:
- 记录日志时用
log.error("query order by id={} failed", orderId, e),SLF4J 会自动展开完整 cause 链 - Spring 中可通过
@ControllerAdvice统一拦截DataAccessException,返回标准 JSON(如{"code":5001,"message":"系统繁忙"}) - 前端不展示堆栈或数据库字段名;错误码由 Controller 映射 HTTP 状态(如 500)或业务状态(如 "DB_UNAVAILABLE")
注意事务与回滚边界
数据库异常封装后,仍需关注 Spring 事务行为:
-
RuntimeException子类默认触发事务回滚,符合多数场景(如插入失败需撤回) - 若某类数据库异常属于“预期可恢复”分支(例如查不到记录只是流程继续),不应包装为
DataAccessException,而应直接处理或返回空/默认值 - 避免在 service 方法中吞掉异常却不记录——封装 ≠ 静默
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











