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

Spring Boot Test中H2数据库主键约束冲突问题排查求助

H2内存数据库插入数据触发主键约束冲突排查

生产环境使用PostgreSQL时,数据库脚本、实体类及Repository均正常运行。搭建Spring Boot测试环境采用H2内存数据库,配置与代码如下:

相关配置与代码

schema-h2.sql(src/test/resources)

CREATE TABLE IF NOT EXISTS project.general_types(
  id SMALLINT PRIMARY KEY NOT NULL,
  name VARCHAR(15) NOT NULL);

data.sql(src/test/resources)

INSERT INTO project.general_types(id, name) VALUES 
   (0, 'Text'),
   (1, 'Binary');

application-test.yml

spring:
  datasource:
    url: jdbc:h2:mem:testdb;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=false;
    username: sa
    password: 
    driver-class-name: org.h2.Driver
    initialization-mode: always
    platform: h2    
  jpa:
    generate-ddl: false
    defer-datasource-initialization: true
    show-sql: true
    open-in-view: false
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        format_sql: true
        default_schema: project
        dialect: org.hibernate.dialect.H2Dialect  
        globally_quoted_identifiers: true

实体类

@Entity(name = "general_types")
public class GeneralTypesEntity {

  @Id
  private short id;
  @Basic
  private String name;

// getters/setters omitted...
}

测试用例

@SpringBootTest
@ActiveProfiles("test")
class GeneralTypesDbTest {

  @Autowired
  private GeneralTypesRepository generalTypesRepository;

  @Test
  void test() {
    assertNotNull(generalTypesRepository);
    assertEquals(2, generalTypesRepository.count());
  }
}

错误现象

仅使用schema-h2.sql时测试正常,但添加data.sql执行插入语句后,出现主键约束冲突:

Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PRIMARY KEY ON project.general_types(id) [0, 'Text']"; SQL statement:
INSERT INTO project.general_types(id, name) VALUES (0, 'Text'), (1, 'Binary') [23505-200]
   at org.h2.message.DbException.getJdbcSQLException(DbException.java:459) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.message.DbException.getJdbcSQLException(DbException.java:429) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.message.DbException.get(DbException.java:205) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.message.DbException.get(DbException.java:181) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.mvstore.db.MVPrimaryIndex.add(MVPrimaryIndex.java:127) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.mvstore.db.MVTable.addRow(MVTable.java:531) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.command.dml.Insert.insertRows(Insert.java:195) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.command.dml.Insert.update(Insert.java:151) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.command.CommandContainer.update(CommandContainer.java:198) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.command.Command.executeUpdate(Command.java:251) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:228) ~[h2-1.4.200.jar:1.4.200]
   at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:201) ~[h2-1.4.200.jar:1.4.200]
   at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:94) ~[HikariCP-4.0.3.jar:na]
   at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java) ~[HikariCP-4.0.3.jar:na]
   at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:261) ~[spring-jdbc-5.3.18.jar:5.3.18]
   ... 96 common frames omitted

将插入语句移至schema-h2.sql中表创建语句后仍报错。

原因分析

核心问题是初始化脚本被重复执行,导致相同主键数据被插入两次:

  1. 配置中同时使用了spring.datasource.initialization-mode: always和spring.jpa.defer-datasource-initialization: true,前者触发Spring JDBC执行初始化脚本,后者延迟数据源初始化至Hibernate启动后,可能再次触发脚本执行;
  2. Spring Boot 2.5+版本中,spring.datasource.initialization-mode已被废弃,改用spring.sql.init.*配置,旧配置与新的延迟初始化特性叠加,容易引发重复执行问题;
  3. 即使将插入语句合并到schema-h2.sql,只要脚本被重复执行,就会触发主键冲突。

解决方案

方案1:修改插入语句为幂等操作

使用H2支持的MERGE语句替代INSERT,确保即使重复执行也不会报错:

MERGE INTO project.general_types(id, name) KEY(id)
VALUES 
   (0, 'Text'),
   (1, 'Binary');

方案2:调整Spring Boot配置,避免脚本重复执行

将旧的数据源初始化配置替换为新版spring.sql.init配置,并关闭重复触发的可能:

spring:
  datasource:
    url: jdbc:h2:mem:testdb;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=false;
    username: sa
    password: 
    driver-class-name: org.h2.Driver
  sql:
    init:
      mode: always
      platform: h2
  jpa:
    generate-ddl: false
    defer-datasource-initialization: true
    show-sql: true
    open-in-view: false
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        format_sql: true
        default_schema: project
        dialect: org.hibernate.dialect.H2Dialect  
        globally_quoted_identifiers: true

移除spring.datasource.initialization-mode和spring.datasource.platform,改用spring.sql.init.*配置,确保脚本仅执行一次。

方案3:禁用延迟数据源初始化(如果不需要)

如果不需要延迟数据源初始化到Hibernate之后,可以将spring.jpa.defer-datasource-initialization设为false,避免脚本被Hibernate再次触发:

spring:
  jpa:
    defer-datasource-initialization: false

内容的提问来源于stack exchange,提问作者Stephen Paulin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 22:03:39