必须用 geterrorcode() 判断 ora 错误码,因其稳定、跨版本、不受语言环境影响;ora-00001 对应 1,ora-00060 对应 60,ora-28001 对应 28001,ora-20001 对应 20001;需递归提取根 sqlexception 并查元数据映射约束名到字段名。

必须用 getErrorCode() 判断 ORA 错误码,别碰 getMessage()
Oracle JDBC 驱动把真实错误码(如 ORA-00001 → 1、ORA-00060 → 60、ORA-20001 → 20001)直接写进 SQLException.getErrorCode() 返回值里。这个值跨驱动版本稳定、不随语言环境变化、不会被日志截断或中间件过滤。一旦你改用 getMessage().contains("ORA-00001"),就等于放弃所有稳定性保障——NLS 设置一变、连接池加个前缀、日志框架 trim 空格,匹配立刻失效。
常见错误现象:ex.getErrorCode() 返回 0 或负数(如 -1),说明不是 Oracle 主动抛的业务/约束类异常,而是底层连接中断、超时、驱动初始化失败等场景,需另作处理。
- ORA-00001(唯一约束)→
ex.getErrorCode() == 1 - ORA-00060(死锁)→
ex.getErrorCode() == 60 - ORA-28001(密码过期)→
ex.getErrorCode() == 28001 - ORA-20001(触发器自定义错误)→
ex.getErrorCode() == 20001
Spring/MyBatis/Hibernate 场景下,得先挖出根 SQLException
MyBatis 会把原始 SQLException 包进 PersistenceException 或 DataIntegrityViolationException;Hibernate 更常套成 ConstraintViolationException。但真正带 Oracle 错误码的,永远是那层最底下的 SQLException。
不要只 catch SQLIntegrityConstraintViolationException——WebLogic 或某些连接池可能再把它包进 BatchUpdateException,导致你漏判。
- 统一在全局异常处理器里
catch(Exception.class),而不是按子类分散捕获 - 写个工具方法
extractRootSQLException(Throwable t),循环调用t.getCause()直到找到instanceof SQLException - 别依赖
getCause()一次就完事:有些包装链长达 4–5 层,必须递归到底
拿到 ORA-00001 后,怎么定位是哪个字段重复?
SQLException.getErrorCode() == 1 只告诉你“唯一约束冲突”,但用户需要的是“手机号已被注册”这种提示。Oracle 错误消息里带的约束名(如 SYS_C0012321)是系统生成的,没法直接映射业务语义。
必须反查数据库元数据,把约束名转成字段名:
SELECT cols.column_name, cons.table_name FROM user_constraints cons JOIN user_cons_columns cols ON cons.constraint_name = cols.constraint_name WHERE cons.constraint_name = 'SYS_C0012321'
用 user_constraints 而不是 all_constraints:权限低、速度快,开发/测试环境够用。
- 查出来是
mobile字段 → 返回 “手机号已被注册” - 查出来是
email字段 → 返回 “邮箱已被绑定” - 注意:约束名大小写敏感,且可能来自不同 schema,查询时需确保当前连接用户有对应
user_*视图权限
ORA-00060 死锁重试必须手动控制事务,不能靠 @Transactional
死锁异常发生在事务提交阶段,此时 Spring 的 @Transactional 代理早已返回,AOP 无法捕获。若直接在 service 方法上加 @Retryable,重试会跑在新事务里,旧事务状态已不可控,极可能抛出 Transaction is not active。
重试逻辑本身也容易踩坑:简单 while + try-catch 会把主键冲突、连接超时等非死锁异常也重试,反而放大问题。
- 核心 DML 操作必须拆到独立无事务注解的方法中,由调用方显式用
TransactionTemplate控制 - 每次重试前要重新获取连接,避免复用已标记为
rollbackOnly的连接 - 必须加退避:例如
Thread.sleep((long) Math.pow(2, retryCount) * 10),防止重试风暴加剧锁竞争 - 重试次数建议设为 3,但前提是业务逻辑具备幂等性——比如更新余额前先查当前值再比对
复杂点在于:同一错误码(如 ORA-12545)在不同驱动版本中可能对应不同异常类型,而业务语义映射又必须精准。靠字符串解析或硬类型匹配都撑不住生产环境,只有结构化字段 + 递归提取 + 元数据联动这一条路走得稳。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











