bigywater.tech
Spring · 184

DuplicateKeyException 还是 DataIntegrityViolationException:同一个数据库错误,JdbcTemplate 与 JPA 抛什么

唯一键冲突到底抛 DuplicateKeyException 还是 DataIntegrityViolationException?JdbcTemplate、Spring Data、EntityManager 各抛什么?超长、not null、外键、语法错误、锁超时又各是什么?这篇用自己起的 MySQL 在 Spring Boot 4.0.8 上逐个触发。

三种写法三种异常
同一个重复键:JdbcTemplate 抛 DuplicateKeyException,saveAndFlush 抛 DataIntegrityViolationException,直接用 EntityManager 抛 Hibernate 的 ConstraintViolationException
归成一类
超长、not null、外键在 Spring 层都是 DataIntegrityViolationException,要靠原因链和信息区分
锁超时是 Transient
锁等待超时抛 CannotAcquireLockException,属于 TransientDataAccessException 分支

重复键

实测 · 本机执行输出
  重复的唯一键 email = a@x.com
    JdbcTemplate:DuplicateKeyException(继承链 DuplicateKeyException → DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 SQLIntegrityConstraintViolationException;首行信息:StatementCallback; SQL [insert into acct values (2, 'a@x.com', 'bbb', null)]; Duplicate entry 'a@x.com' for key 'acct.email'
    JPA 直接用 EntityManager.persist 加 flush:ConstraintViolationException(继承链 ConstraintViolationException → JDBCException → HibernateException → PersistenceException → );原因链 SQLIntegrityConstraintViolationException;首行信息:could not execute statement [Duplicate entry 'a@x.com' for key 'acct.email'] [insert into acct (email,name,parent_id,id) valu
    Spring Data 的 saveAndFlush:DataIntegrityViolationException(继承链 DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 ConstraintViolationException ← SQLIntegrityConstraintViolationException;首行信息:could not execute statement [Duplicate entry 'a@x.com' for key 'acct.email'] [insert into acct (email,name,parent_id,id) valu

三种写法抛三种异常。

超长

实测 · 本机执行输出
  name 列是 varchar(5),写入 7 个字符
    JdbcTemplate:DataIntegrityViolationException(继承链 DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 MysqlDataTruncation;首行信息:StatementCallback; SQL [insert into acct values (2, 'b@x.com', 'abcdefg', null)]; Data truncation: Data too long for column '
    JPA 直接用 EntityManager.persist 加 flush:DataException(继承链 DataException → JDBCException → HibernateException → PersistenceException → );原因链 MysqlDataTruncation;首行信息:could not execute statement [Data truncation: Data too long for column 'name' at row 1] [insert into acct (email,name,parent_
    Spring Data 的 saveAndFlush:DataIntegrityViolationException(继承链 DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 DataException ← MysqlDataTruncation;首行信息:could not execute statement [Data truncation: Data too long for column 'name' at row 1] [insert into acct (email,name,parent_

DataIntegrityViolationException,原因 MysqlDataTruncation。

not null

实测 · 本机执行输出
  name 列 not null,写入 null
    JdbcTemplate:DataIntegrityViolationException(继承链 DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 SQLIntegrityConstraintViolationException;首行信息:StatementCallback; SQL [insert into acct values (2, 'b@x.com', null, null)]; Column 'name' cannot be null
    JPA 直接用 EntityManager.persist 加 flush:ConstraintViolationException(继承链 ConstraintViolationException → JDBCException → HibernateException → PersistenceException → );原因链 SQLIntegrityConstraintViolationException;首行信息:could not execute statement [Column 'name' cannot be null] [insert into acct (email,name,parent_id,id) values (?,?,?,?)]
    Spring Data 的 saveAndFlush:DataIntegrityViolationException(继承链 DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 ConstraintViolationException ← SQLIntegrityConstraintViolationException;首行信息:could not execute statement [Column 'name' cannot be null] [insert into acct (email,name,parent_id,id) values (?,?,?,?)]; SQL

DataIntegrityViolationException。

外键

实测 · 本机执行输出
  parent_id 外键指向不存在的 id 99
    JdbcTemplate:DataIntegrityViolationException(继承链 DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 SQLIntegrityConstraintViolationException;首行信息:StatementCallback; SQL [insert into acct values (2, 'b@x.com', 'bbb', 99)]; Cannot add or update a child row: a foreign key c
    JPA 直接用 EntityManager.persist 加 flush:ConstraintViolationException(继承链 ConstraintViolationException → JDBCException → HibernateException → PersistenceException → );原因链 SQLIntegrityConstraintViolationException;首行信息:could not execute statement [Cannot add or update a child row: a foreign key constraint fails (`demo`.`acct`, CONSTRAINT `acc
    Spring Data 的 saveAndFlush:DataIntegrityViolationException(继承链 DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 ConstraintViolationException ← SQLIntegrityConstraintViolationException;首行信息:could not execute statement [Cannot add or update a child row: a foreign key constraint fails (`demo`.`acct`, CONSTRAINT `acc
  删除被引用的父行
    JdbcTemplate delete id=1:DataIntegrityViolationException(继承链 DataIntegrityViolationException → NonTransientDataAccessException → DataAccessException);原因链 SQLIntegrityConstraintViolationException;首行信息:StatementCallback; SQL [delete from acct where id = 1]; Cannot delete or update a parent row: a foreign key constraint fails

插入与删除都是 DataIntegrityViolationException。

语法错误

实测 · 本机执行输出
  SQL 语法错误 / 表不存在 / 列不存在
    JdbcTemplate 语法错误(form 写成了 from 的位置):BadSqlGrammarException(继承链 BadSqlGrammarException → InvalidDataAccessResourceUsageException → NonTransientDataAccessException → DataAccessException);原因链 SQLSyntaxErrorException;首行信息:StatementCallback; bad SQL grammar [select * form acct]
    JdbcTemplate 表不存在:BadSqlGrammarException(继承链 BadSqlGrammarException → InvalidDataAccessResourceUsageException → NonTransientDataAccessException → DataAccessException);原因链 SQLSyntaxErrorException;首行信息:StatementCallback; bad SQL grammar [select * from nope]
    JdbcTemplate 列不存在:BadSqlGrammarException(继承链 BadSqlGrammarException → InvalidDataAccessResourceUsageException → NonTransientDataAccessException → DataAccessException);原因链 SQLSyntaxErrorException;首行信息:StatementCallback; bad SQL grammar [select nope from acct]
    JPA 原生 SQL 列不存在:SQLGrammarException(继承链 SQLGrammarException → JDBCException → HibernateException → PersistenceException → );原因链 SQLSyntaxErrorException;首行信息:JDBC exception executing SQL [Unknown column 'nope' in 'field list'] [select nope from acct]

BadSqlGrammarException。

锁超时

实测 · 本机执行输出
  事务 A 锁住 id=1 的行并持有 3 秒,事务 B 更新同一行(服务端 innodb_lock_wait_timeout=1)
    JdbcTemplate:CannotAcquireLockException(继承链 CannotAcquireLockException → PessimisticLockingFailureException → ConcurrencyFailureException → TransientDataAccessException → DataAccessException);原因链 MySQLTransactionRollbackException;首行信息:StatementCallback; SQL [update acct set name = 'zzz' where id = 1]; Lock wait timeout exceeded; try restarting transaction

CannotAcquireLockException,Transient 分支。

分步演示

ExcTranslate
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Boot 4.0.8,Spring Framework 7.0.9,Hibernate 7.2,MySQL 驱动 mysql-connector-j,自己起的 MySQL 实例,innodb_lock_wait_timeout=1;表 acct 有主键、email 唯一、name varchar(5) not null、parent_id 外键;「直接用 EntityManager」指在 TransactionTemplate 里调用 persist 与 flush,没有经过 @Repository 或 Spring Data 代理)
    • 没有验证:加了 @Repository 的类里直接用 EntityManager 时是否也被翻译成 DataIntegrityViolationException;死锁(DeadlockLoserDataAccessException)与 DuplicateKeyException 在 JPA 路径上的判断;PostgreSQL 与 H2 上的异常类型;JdbcClient 的异常;Spring 异常翻译读取 SQLState 与 vendor code 的具体规则;MySQL 之外的唯一键冲突是否也映射为 DuplicateKeyException
    京ICP备2026067502号-1京公网安备11010502063487号