You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:57:15