应使用全局异常处理器精准捕获DataIntegrityViolationException,结合SQLState码或错误信息关键词(如“Duplicate entry”“duplicate key”)识别主键/唯一冲突,统一返回400提示;同时通过INSERT IGNORE、ON CONFLICT或序列同步等数据库机制前置规避,避免依赖业务层try-catch。

Spring Boot 中数据库主键冲突(如 MySQL 唯一索引冲突、PostgreSQL 主键重复)不能靠通用 Exception 捕获来兜底,而应分层识别、精准拦截、统一响应。核心思路是:**不依赖 try-catch 散布在业务代码里,而是用全局异常处理器捕获特定数据访问异常类型,并结合数据库机制做前置预防。**
识别并捕获对应的数据层异常类型
不同数据库抛出的主键/唯一约束冲突异常类名不同,但 Spring Data JDBC / JPA 会将其统一包装为标准异常,需针对性捕获:
-
MySQL 唯一索引冲突:通常触发
DataIntegrityViolationException,其根本原因是SQLState = 23000(完整性约束违规),内部 cause 往往是MySQLSyntaxErrorException或CommunicationsException;可通过检查getRootCause()+ SQL 状态码进一步确认 -
PostgreSQL 主键冲突:同样抛
DataIntegrityViolationException,但底层 cause 多为PSQLException,错误详情中包含"duplicate key value violates unique constraint"字样 -
JPA/Hibernate 场景下:若使用
@GeneratedValue(strategy = GenerationType.IDENTITY),插入失败后也会归为此异常;若用SEQUENCE且序列不同步,同样落入该异常范畴
在 @RestControllerAdvice 中集中处理
定义全局异常处理器,专门拦截 DataIntegrityViolationException,并返回结构化提示:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
@ExceptionHandler(DataIntegrityViolationException.class)
public Result<Void> handleDataIntegrityViolation(DataIntegrityViolationException e, HttpServletRequest request) {
String message = e.getRootCause() instanceof SQLException sqlEx
? sqlEx.getSQLState()
: e.getMessage();
// 精准识别主键/唯一冲突(兼容 MySQL & PostgreSQL)
if (message.contains("Duplicate entry") ||
message.contains("duplicate key") ||
"23000".equals(sqlEx.getSQLState())) {
log.warn("主键或唯一约束冲突,接口:{},参数:{}", request.getRequestURI(), request.getParameterMap());
return Result.error(400, "数据已存在,请勿重复提交");
}
log.error("数据完整性异常,接口:{}", request.getRequestURI(), e);
return Result.error(500, "系统繁忙,请稍后再试");
}
配合数据库机制做主动规避
异常捕获是兜底手段,真正减少冲突要从源头控制:
-
MySQL 场景:避免在 insert 前查重再 insert(存在竞态),改用
INSERT IGNORE或ON DUPLICATE KEY UPDATE;或使用SELECT ... FOR UPDATE加行锁(慎用,影响并发) -
PostgreSQL 场景:定期检查并同步序列值,例如执行
SELECT setval('user_id_seq', (SELECT MAX(id) FROM user));;上线前或批量导入后务必运行 - 通用建议:在 Service 层对关键字段(如 username、phone、email)做幂等校验(如 Redis SETNX + 过期时间),把冲突拦截在 DB 写入之前
补充:日志与敏感信息控制
主键冲突属于可预期的业务异常,记录 WARN 级日志即可,不打印完整堆栈;响应体中禁止暴露表名、字段名、SQL 片段等敏感信息,只返回用户能理解的提示,例如“手机号已被注册”,而非“违反 users.phone_unique 约束”。

















