Spring Boot+JPA+H2新增待办记录主键重复异常求助
主键重复异常排查与解决(Spring Boot + H2 + JPA)
问题描述
使用Java、Spring Boot、H2数据库和Spring Data JPA开发待办事项后端时,插入新记录触发主键/唯一键重复异常。已在ToDo实体类的id字段标注@Id和@GeneratedValue(strategy = GenerationType.IDENTITY),但问题仍未解决。
相关代码
实体类ToDo.java
import jakarta.persistence.*; @Entity public class ToDo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private boolean completed; public ToDo() {} public ToDo(String title, boolean completed) { this.title = title; this.completed = completed; } // getter、setter省略 }
仓库接口ToDoRepository.java
import org.springframework.data.jpa.repository.JpaRepository; public interface ToDoRepository extends JpaRepository<ToDo, Long> { }
控制器ToDoController.java
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class ToDoController { private final ToDoRepository toDoRepository; public ToDoController(ToDoRepository toDoRepository) { this.toDoRepository = toDoRepository; } @PostMapping("/todos") public ToDo createTodo(@RequestBody ToDo todo) { return toDoRepository.save(todo); } }
异常栈信息
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:267) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:230) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:164) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218) at jdk.proxy2/jdk.proxy2.$Proxy83.save(Unknown Source) at com.example.todo.ToDoController.createTodo(ToDoController.java:20) // 省略后续栈信息 Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59) at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:37) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3221) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3827) at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:107) at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478) at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721) at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475) at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:377) at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107) at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1407) at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:489) at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3290) at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2425) at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:449) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562) ... 15 more Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.TODO(ID)"; SQL statement: insert into todo (completed, title, id) values (?, ?, ?) [23505-224] at org.h2.message.DbException.getJdbcSQLException(DbException.java:521) at org.h2.message.DbException.getJdbcSQLException(DbException.java:496) at org.h2.message.DbException.get(DbException.java:227) at org.h2.message.DbException.get(DbException.java:203) at org.h2.index.Index.getDuplicateKeyException(Index.java:401) at org.h2.mvstore.db.MVSecondaryIndex.add(MVSecondaryIndex.java:281) at org.h2.mvstore.db.MVTable.addRow(MVTable.java:547) at org.h2.command.dml.Insert.insertRows(Insert.java:182) at org.h2.command.dml.Insert.update(Insert.java:134) at org.h2.command.CommandContainer.update(CommandContainer.java:169) at org.h2.command.Command.executeUpdate(Command.java:252) at org.jdbcdslog.StatementLoggingProxy.executeUpdate(StatementLoggingProxy.java:402) at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:219) at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:183) at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197) ... 35 more
排查与解决步骤
1. 强制重置请求实体的id为null
从异常SQL可以看到,插入语句带上了id参数,说明传入的ToDo对象id不为null,JPA不会触发自动生成逻辑。直接在控制器中重置id:
@PostMapping("/todos") public ToDo createTodo(@RequestBody ToDo todo) { todo.setId(null); // 让数据库自动生成主键 return toDoRepository.save(todo); }
同时要求前端请求不要携带id字段。
2. 验证H2表结构的自增配置
通过H2控制台查看TODO表的ID字段是否为AUTO_INCREMENT。如果不是,手动执行SQL修改:
ALTER TABLE TODO ALTER COLUMN ID SET GENERATED ALWAYS AS IDENTITY;
开发环境也可以设置spring.jpa.hibernate.ddl-auto=create重新生成表结构。
3. 检查实体类注解包路径
Spring Boot 3.x必须使用jakarta.persistence包下的注解,不要误用旧的javax.persistence,否则注解不会生效。
4. 修正初始化数据的主键问题
如果项目启动时通过data.sql插入了测试数据,不要手动指定id值;如果已经指定,需要同步更新自增序列起始值:
ALTER TABLE TODO ALTER COLUMN ID RESTART WITH 3; -- 假设已插入2条记录
内容的提问来源于stack exchange,提问作者Suraj Singh
相关产品推荐
相关产品推荐

