SpringBootTest集成H2内存库执行data.sql报ID列不允许为NULL错误
SpringBoot单元测试H2模拟Oracle插入报ID非空错误
问题场景
使用SpringBoot 2.7.0做单元测试时,采用H2内存数据库模拟生产环境Oracle11g,通过data.sql初始化测试数据,执行插入操作时抛出NULL not allowed for column "ID"报错。已尝试在INSERT语句中显式指定Id字段并配置default默认值、在JDBC连接URL中设置Mode=LEGACY,均未解决问题。
相关代码与配置
data.sql 初始化脚本
INSERT INTO CSM_SECURITY.csm_person(EMAIL, PERSON_NAME, SYS_ADMIN, DEFAULT_LANGUAGE, WSO2_ID, SYS_ADM_COUNTRY_ID, SYS_ADM_COUNTRY) VALUES ('admin@wso2.com','admin','Y','es-ar',NULL,NULL,NULL);
Person 实体类定义
@Entity @Table(name = "csm_person", uniqueConstraints = @UniqueConstraint(columnNames = "email")) public class Person implements Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PERSON_SEQ") @SequenceGenerator(sequenceName = "CSM_PERSON_ID_SEQ", allocationSize = 1, name = "PERSON_SEQ") private Long id; @Column(name = "email") private String email; @Column(name = "person_name") private String personName; // 其余属性省略 ... }
测试环境配置 src/test/resources/application.properties
# SQL properties spring.jpa.defer-datasource-initialization=true spring.jpa.database-platform = org.hibernate.dialect.H2Dialect spring.jpa.show-sql = true spring.jpa.properties.hibernate.format_sql = true spring.jpa.properties.hibernate.default_schema = CSM_SECURITY spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect spring.jpa.properties.hibernate.event.merge.entity_copy_observer = allow spring.jpa.open-in-view = false spring.datasource.driver-class-name = org.h2.Driver spring.sql.init.platform = h2 spring.datasource.name = CSM_SECURITY spring.datasource.url = jdbc:h2:mem:CSM_SECURITY;MODE=Oracle;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS CSM_SECURITY spring.sql.init.mode = embedded spring.datasource.username = sa
pom.xml 父依赖配置
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> <relativePath/> <!-- lookup parent from repository --> </parent>
完整报错信息
Caused by: org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of URL [file:/home/ainacio/Development/csm-security/target/test-classes/data.sql]: INSERT INTO CSM_SECURITY.csm_person(EMAIL, PERSON_NAME, SYS_ADMIN, DEFAULT_LANGUAGE, WSO2_ID, SYS_ADM_COUNTRY_ID, SYS_ADM_COUNTRY) VALUES ('admin@wso2.com','admin','Y','es-ar',NULL,NULL,NULL); nested exception is org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement:
问题根因
报错核心是执行data.sql的原生INSERT时,不会触发JPA/Hibernate配置的主键生成逻辑:
- 实体类上的
@GeneratedValue序列主键策略,仅在Hibernate自身做持久化操作时生效 - Spring执行data.sql是直接通过JDBC提交原生SQL,此时ID字段没有配置关联序列的默认值,插入时又未显式传入ID值,H2直接给ID赋值NULL,触发主键非空约束
- 之前尝试的
Mode=LEGACY是H2旧版本兼容参数,和Oracle模式下的序列默认值逻辑无关;配置default默认值时如果没有正确关联CSM_PERSON_ID_SEQ序列,也不会生效。
解决方案
两种方案任选其一即可,优先选第一种和生产Oracle行为对齐:
- 方案1:通过schema.sql显式定义表结构,给ID字段绑定序列默认值
在test/resources下新建schema.sql,手动创建序列和表,给ID列配置序列默认值,和生产Oracle逻辑保持一致:
配置后不需要修改现有data.sql的INSERT语句,插入时不传ID会自动生成,和生产环境行为完全一致。-- 建序列 CREATE SEQUENCE IF NOT EXISTS CSM_SECURITY.CSM_PERSON_ID_SEQ START WITH 1 INCREMENT BY 1; -- 建表,ID字段默认取序列下一个值 CREATE TABLE IF NOT EXISTS CSM_SECURITY.csm_person ( id BIGINT DEFAULT CSM_SECURITY.CSM_PERSON_ID_SEQ.NEXTVAL NOT NULL PRIMARY KEY, email VARCHAR(255), person_name VARCHAR(255), sys_admin VARCHAR(1), default_language VARCHAR(20), wso2_id VARCHAR(255), sys_adm_country_id BIGINT, sys_adm_country VARCHAR(255), -- 其余字段按实体类定义补全即可 CONSTRAINT uk_csm_person_email UNIQUE (email) ); - 方案2:修改INSERT语句,显式调用序列生成ID
如果不想手写建表语句,直接修改data.sql的INSERT语句,插入时显式取序列值作为ID传入:INSERT INTO CSM_SECURITY.csm_person(ID, EMAIL, PERSON_NAME, SYS_ADMIN, DEFAULT_LANGUAGE, WSO2_ID, SYS_ADM_COUNTRY_ID, SYS_ADM_COUNTRY) VALUES (CSM_SECURITY.CSM_PERSON_ID_SEQ.NEXTVAL, 'admin@wso2.com','admin','Y','es-ar',NULL,NULL,NULL);
注意:不要依赖Hibernate自动建表生成的结构,Hibernate在H2 Oracle兼容模式下自动建表时,不会给主键列自动绑定序列默认值,原生SQL执行时依然会报ID为空的错误。
内容的提问来源于stack exchange,提问作者Aldo Inácio da Silva
相关产品推荐
相关产品推荐

