MyBatis 将 SQL 异常映射为语义化 RuntimeException(如 DuplicateKeyException),应在 DAO 层精准捕获特定异常而非泛化 catch;需按子类优先顺序处理,且不可吞没异常,须向 Service 层传递错误信号。

MyBatis 本身不封装 SQL 异常为自定义业务异常,而是将底层 JDBC 异常(如 SQLException)经由 MyBatis 的异常转换机制,映射为更语义化的运行时异常,例如 DuplicateKeyException、SQLGrammarException、DataIntegrityViolationException 等。这些都属于 RuntimeException 子类,无需强制 try-catch,但**在 DAO 层有针对性地捕获它们是合理且常见的做法**,尤其用于识别主键/唯一约束冲突等明确业务含义的错误。
明确要捕获的具体异常类型
不要写 catch (Exception e) 或 catch (RuntimeException e) —— 这会掩盖真正问题,也违背精准处理原则。MyBatis 将重复键异常统一转为 org.springframework.dao.DuplicateKeyException(注意:这是 Spring JDBC/MyBatis 集成层抛出的,不是原生 SQLException)。其他常见映射关系包括:
-
DuplicateKeyException:主键或唯一索引冲突(最常用) -
DataIntegrityViolationException:外键约束失败、字段非空校验失败等 -
SQLGrammarException:SQL 语法错误或表/列名不存在 -
CannotAcquireLockException:数据库锁超时(如高并发 update)
DAO 方法中用 try-catch 包裹单条操作
try 块应仅包含一次 insert/update/delete 调用,避免包裹无关逻辑。捕获后可返回业务码、抛出自定义异常或记录日志:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 直接返回状态码(适合简单服务层):
if (e instanceof DuplicateKeyException) { return -2; } - 包装为业务异常向上抛出(推荐,利于统一错误处理):
throw new BizException("用户名已存在", ErrorCode.USER_EXISTS); - 记录结构化日志并继续抛出(便于排查):
log.warn("插入用户失败,主键重复: {}", user.getId(), e); throw e;
注意异常继承关系与 catch 顺序
DuplicateKeyException 是 DataIntegrityViolationException 的子类,而后者又继承自 RuntimeException。若需同时处理多种数据异常,必须按“子类优先”顺序书写 catch 块:
立即学习“Java免费学习笔记(深入)”;
- ✅ 正确顺序:
catch (DuplicateKeyException e) { ... }
catch (DataIntegrityViolationException e) { ... } - ❌ 错误顺序(后者永远无法触发):
catch (DataIntegrityViolationException e) { ... }
catch (DuplicateKeyException e) { ... }
不建议在 DAO 层吞掉异常或只打日志
DAO 是数据访问边界,它的职责是执行和反馈结果。如果 catch 后仅调用 e.printStackTrace() 或 log.info(...) 而不抛出/返回,会导致上层完全感知不到失败,可能引发数据不一致或静默失败。务必确保异常信号能穿透到 Service 层做决策(重试、降级、提示用户等)。

















