异常转译是将底层技术异常(如sqlexception)包装为语义明确的业务异常(如usernotfoundexception),保留cause链、暴露业务上下文、避免泄露技术细节。

什么是异常转译:不是“吞掉异常”,而是“换种说法说清楚”
异常转译不是掩盖问题,而是把底层抛出的、调用方看不懂的异常(比如 SQLException 或 NoSuchElementException),包装成上层语义明确的异常(比如 DataAccessException 或 IndexOutOfBoundsException)。关键在于:保留原始错误原因(用 cause 链),但暴露更贴合业务上下文的类型和消息。
为什么必须做异常转译:客户端不关心 JDBC,只关心“查不到用户”
直接把数据库驱动抛出的 SQLException 一路向上扔,业务层就得写一堆 if-else 判断 SQLState 码;而用 Spring 的 @Repository + 异常转译后,统一拿到 EmptyResultDataAccessException,一眼就懂是“没查到数据”,不用翻日志查 SQL 错误码。
- 避免暴露技术细节:客户端代码不该依赖
com.mysql.cj.jdbc.exceptions.MySQLTimeoutException这类实现类 - 统一错误分类:所有 DAO 层失败都归为
DataAccessException子类,便于全局@ExceptionHandler拦截 - 保留调试线索:通过
getCause()仍能追溯原始异常栈,不影响问题定位
怎么手动做异常转译:别只 throw new XxxException("xxx")
手动转译时最容易犯的错,就是丢掉原始异常。正确做法是显式传入 cause 参数,构建异常链:
try {
return jdbcTemplate.queryForObject(sql, rowMapper, id);
} catch (EmptyResultDataAccessException e) {
// ✅ 正确:保留 cause,方便追踪
throw new UserNotFoundException("用户不存在,ID=" + id, e);
// ❌ 错误:丢失原始异常信息
// throw new UserNotFoundException("用户不存在,ID=" + id);
}
- 自定义异常构造函数必须接收
Throwable cause并调用super(message, cause) - 不要在 catch 块里只写
e.printStackTrace()就 throw 新异常——这等于白转译 - 如果原始异常本身已含充分上下文(如
IllegalArgumentException),可直接重抛,无需转译
@Repository 怎么自动帮你转译:它不只是个标记
@Repository 不仅是告诉 Spring “这是 DAO Bean”,更关键的是触发了 Spring 的异常翻译机制——只要你的 DAO 方法抛出 JDBC/Hibernate 异常,Spring 就会自动把它转成 DataAccessException 体系里的标准子类。
- 前提:DAO 类必须被
@Repository标记,且所在包被@ComponentScan扫描到 - 不需要额外配置:Spring Boot 默认启用
PersistenceExceptionTranslationPostProcessor - 注意陷阱:如果你在 DAO 方法里自己 try-catch 了底层异常又没 rethrow,转译就失效了
throw new XxxException(e),而是判断哪一层该转、哪一层该透传——底层异常是否真的对上层有意义?这个边界感,比语法更重要。










