1. 概述

在Spring Boot应用开发中,特别是在集成测试阶段,使用H2内存数据库能提供轻量级、高效的数据库交互模拟方案。作为开发者,我们经常需要在测试过程中初始化数据库结构、预加载数据或执行自定义SQL脚本。

本文将介绍在Spring Boot测试环境中使用H2数据库执行SQL脚本的几种常见方法,帮助大家高效管理测试数据。

2. 通过JDBC URL指定脚本

H2的一个实用特性是允许在数据库初始化时直接通过JDBC URL自动执行SQL脚本。下面通过示例说明具体用法。

首先,**在resources/sql目录下创建SQL文件init_my_db.sql**:

CREATE TABLE TASK_TABLE
(
    ID   INT PRIMARY KEY,
    NAME VARCHAR(255)
);

INSERT INTO TASK_TABLE (ID, NAME) VALUES (1, '启动应用');
INSERT INTO TASK_TABLE (ID, NAME) VALUES (2, '检查数据表是否填充');

该脚本创建表并插入两条记录。接下来在Spring Boot应用中配置H2自动执行此脚本:

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:demodb;INIT=RUNSCRIPT FROM 'classpath:/sql/init_my_db.sql'
    username: sa
    password:

关键点在于在JDBC URL中添加INIT子句

INIT=RUNSCRIPT FROM 'classpath:/sql/init_my_db.sql'

RUNSCRIPT FROM <script_path>是H2的专用命令,用于执行指定脚本。上述配置会在内存数据库启动时自动执行resources/sql/init_my_db.sql,非常适合初始化测试数据。

验证脚本是否执行的测试代码:

List<String> expectedTaskNames = List.of("启动应用", "检查数据表是否填充");
List<String> taskNames = entityManager.createNativeQuery("SELECT NAME FROM TASK_TABLE ORDER BY ID")
  .getResultStream()
  .map(Object::toString)
  .toList();
assertEquals(expectedTaskNames, taskNames);

测试通过即证明脚本已执行。注意:

  • RUNSCRIPT命令支持Java类路径(如classpath:/sql/...
  • ✅ 使用file:前缀可指定绝对路径(如RUNSCRIPT FROM 'file:/opt/scripts/init.sql'

3. Spring Boot内置脚本检测:schema.sql和data.sql

除了H2原生方案,当使用Spring Data JPA时,Spring Boot会自动检测并执行两个特殊文件

  • schema.sql:定义数据库结构(表、视图等)
  • data.sql:填充初始数据

无需修改JDBC URL,只需将SQL语句放入对应文件即可。下面通过示例说明:

首先创建resources/schema.sql

CREATE TABLE CITY
(
    ID   INT PRIMARY KEY ,
    NAME VARCHAR(255)
);

然后创建resources/data.sql

INSERT INTO CITY (ID, NAME) VALUES (1, '纽约');
INSERT INTO CITY (ID, NAME) VALUES (2, '汉堡');
INSERT INTO CITY (ID, NAME) VALUES (3, '上海');

⚠️ 执行顺序固定:Spring Boot先执行schema.sql,再执行data.sql

验证自动执行的测试代码:

List<String> expectedCityNames = List.of("纽约", "汉堡", "上海");
List<String> cityNames = entityManager.createNativeQuery("SELECT NAME FROM CITY ORDER BY ID")
  .getResultStream()
  .map(Object::toString)
  .toList();
assertEquals(expectedCityNames, cityNames);

高级配置

如果需要自定义文件位置,可通过以下配置实现:

spring:
  sql:
    init:
      schema-locations: classpath:/custom/schema.sql
      data-locations: classpath:/custom/data.sql

禁用自动检测(必要时):

spring:
  sql:
    init:
      mode: never

4. 通过EntityManager动态执行脚本

有时需要程序化执行SQL脚本(如重置测试数据),可通过原生查询调用H2的RUNSCRIPT命令实现。

复用前面的CITY表,创建resources/sql/add_cities.sql

INSERT INTO CITY (ID, NAME) VALUES (4, '巴黎');
INSERT INTO CITY (ID, NAME) VALUES (5, '柏林');
INSERT INTO CITY (ID, NAME) VALUES (6, '东京');

动态执行脚本的测试代码:

entityManager.createNativeQuery("RUNSCRIPT FROM 'classpath:/sql/add_cities.sql'")
  .executeUpdate();
List<String> expectedCityNames = List.of("纽约", "汉堡", "上海", "巴黎", "柏林", "东京");
List<String> cityNames = entityManager.createNativeQuery("SELECT NAME FROM CITY ORDER BY ID")
  .getResultStream()
  .map(Object::toString)
  .toList();
assertEquals(expectedCityNames, cityNames);

⚠️ 关键注意事项

  • 必须调用executeUpdate()执行脚本
  • ❌ 即使脚本包含SELECT语句,调用getResultList()也会抛出异常

5. 总结

本文介绍了在H2数据库中执行SQL脚本的三种核心方法:

  1. JDBC URL初始化:通过INIT=RUNSCRIPT实现数据库启动时自动执行
  2. Spring Boot自动检测:利用schema.sqldata.sql简化配置
  3. 动态执行:通过EntityManager实现程序化脚本调用

每种方法适用于不同场景:

  • ✅ JDBC URL方案适合简单初始化
  • ✅ Spring Boot内置方案适合标准JPA项目
  • ✅ 动态执行适合需要灵活控制的测试场景

合理运用这些策略,能显著提升集成测试的维护性和应用健壮性。完整示例代码可在GitHub仓库获取。


原始标题:Executing SQL Scripts in H2 Database | Baeldung