异常转译是将底层异常(如SQLException)转换为业务语义明确的自定义异常,以解耦技术细节、增强可读性与可维护性;需保留原始异常作为cause,分层转译、优先使用unchecked异常,并可借助Spring@Repository自动完成部分转译。

异常转译(Exception Translation)是Java中一种重要的异常处理设计模式,核心思想是:**将底层抛出的原始异常(如数据库驱动异常、IO异常等)转换为上层业务更易理解、更符合抽象层次的自定义异常**,避免暴露技术细节,同时保持调用链的语义清晰。
为什么要进行异常转译
直接抛出底层异常(如 SQLException、IOException)会导致:
- 上层代码被迫依赖具体实现技术(比如必须 import java.sql.*)
- 业务逻辑与数据访问细节紧耦合,难以替换持久层(如从 JDBC 换成 JPA)
- 异常信息不具业务含义(如 “ORA-00942: table or view does not exist” 对服务调用方无意义)
- 违反“封装”和“关注点分离”原则
如何实现异常转译(标准做法)
典型方式是在 DAO 或 Repository 层捕获底层异常,并封装为业务语义明确的运行时异常(通常继承 RuntimeException),同时保留原始异常作为 cause:
public User findById(Long id) {
try {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new UserRowMapper(), id);
} catch (EmptyResultDataAccessException e) {
throw new UserNotFoundException("User not found with id: " + id, e);
} catch (DataAccessException e) {
throw new DataAccessException("Failed to load user", e);
}
}其中 UserNotFoundException 是自定义异常,构造器需支持传入 cause:
立即学习“Java免费学习笔记(深入)”;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}关键实践要点
- 只转译,不吞掉:务必把原始异常作为 cause 传入,便于日志追踪和根因分析
- 分层转译,不越级:DAO 层转译为数据访问异常;Service 层可进一步转译为业务异常(如 InsufficientBalanceException),但不要在 Service 直接 catch SQLException
- 优先使用 unchecked 异常:除非需要强制调用方处理,否则避免 throws SQLException 等检查型异常,减少模板代码
- 提供有意义的错误消息:避免 “An error occurred”,应包含上下文(如操作、ID、关键参数)
配合 Spring 的简化方案
Spring 的 @Repository 注解 + PersistenceExceptionTranslationPostProcessor(默认启用)可自动将 JDBC/Hibernate 异常转为统一的 DataAccessException 层次体系,你只需:
- 标记 DAO 类为 @Repository
- 直接抛出 Spring 的 DataAccessException 子类(如 DuplicateKeyException),或自定义异常继承它
- 必要时在 service 层再做一次业务语义转译


















