1. 概述

Spring Boot 让管理数据库变更变得异常简单。使用默认配置时,它会自动扫描项目中的实体类并创建对应的数据表。

但有时我们需要更精细的控制,这时就能用上 Spring 的 data.sqlschema.sql 文件了。这两个文件能帮我们搞定数据库初始化的"脏活累活",简单粗暴又高效。

2. data.sql 文件

假设我们正在使用 JPA,先定义一个简单的 Country 实体:

@Entity
public class Country {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Integer id;
    
    @Column(nullable = false)
    private String name;

    //...
}

运行应用时,Spring Boot 会自动创建空表,但不会插入任何数据

要加载数据?创建一个 data.sql 文件就搞定:

INSERT INTO country (name) VALUES ('India');
INSERT INTO country (name) VALUES ('Brazil');
INSERT INTO country (name) VALUES ('USA');
INSERT INTO country (name) VALUES ('Italy');

⚠️ 注意:默认情况下 data.sql 在 Hibernate 初始化前执行。但我们需要先建表再插数据,所以得延迟数据源初始化:

spring.jpa.defer-datasource-initialization=true

当这个文件在类路径下时,Spring 会自动用它填充 country 表。

关键提醒:所有基于脚本的初始化(无论是 data.sql 还是下文的 schema.sql)都需要设置:

spring.sql.init.mode=always

对于 H2 这类嵌入式数据库,默认值就是 always

3. schema.sql 文件

有时我们不想依赖 Hibernate 的默认建表机制。这时可以自定义 schema.sql 文件:

create table USERS(
  ID int not null AUTO_INCREMENT,
  NAME varchar(100) not null,
  STATUS int,
  PRIMARY KEY ( ID )
);

Spring 会读取这个文件并创建对应的表结构。即使项目中没有 Users 实体类,数据库里也会出现这个表。

⚠️ 踩坑警告:如果同时使用 Hibernate 自动建表和脚本初始化(schema.sql + data.sql),可能会产生冲突。解决方法:

  1. 完全禁用 Hibernate 的 DDL 功能:
    spring.jpa.hibernate.ddl-auto=none
    
  2. 或者想同时使用两者时:
    spring.jpa.defer-datasource-initialization=true
    
    这样执行顺序就变成了:Hibernate 建表 → schema.sql 补充结构 → data.sql 插入数据

同样别忘了设置:

spring.sql.init.mode=always

4. 使用 Hibernate 控制数据库创建

Spring 提供了 JPA 专用属性 spring.jpa.hibernate.ddl-auto 让 Hibernate 控制 DDL 生成。标准值包括:

行为
create 删除现有表 → 创建新表
update 对比实体与现有表结构 → 只更新差异(永不删除表/列
create-drop 类似 create,但会话结束时自动删表(适合测试)
validate 只验证表/列是否存在,不存在则报错
none 禁用 DDL 生成

Spring Boot 默认值规则:

  • 未检测到 Schema Manager 时 → create-drop
  • 其他情况 → none

务必谨慎设置该值,否则可能踩大坑!

5. 自定义数据库模式创建

默认情况下,Spring Boot 会自动为嵌入式数据源创建模式。想自定义行为?用 spring.sql.init.mode 属性:

行为
always 总是初始化数据库
embedded 仅嵌入式数据库初始化(默认值)
never 从不初始化

✅ 实用技巧:如果用 MySQL/PostgreSQL 等非嵌入式数据库,需要初始化模式时,**必须显式设置为 always**。

该属性在 Spring Boot 2.5.0 引入,旧版本请用 spring.datasource.initialization-mode

6. 使用 @Sql 注解

Spring 提供 @Sql 注解——一种声明式初始化测试数据的方式。主要属性:

属性 作用
config SQL 脚本本地配置
executionPhase 指定执行时机
statements 内联 SQL 语句
scripts SQL 脚本文件路径(同 value

@Sql 可用于类级别或方法级别

6.1. 类级别 @Sql 注解

在测试类上声明 @Sql 可为整个测试加载数据:

@Sql({"/employees_schema.sql", "/import_employees.sql"})
public class SpringBootInitialLoadIntegrationTest {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Test
    public void testLoadDataForTestClass() {
        assertEquals(3, employeeRepository.findAll().size());
    }
}

默认在测试方法前执行(BEFORE_TEST_METHOD)。Spring 6.1+ 和 Spring Boot 3.2+ 新增了类级执行阶段:

@Sql(scripts = {"/employees_schema.sql", "/import_employees.sql"}, 
     executionPhase = BEFORE_TEST_CLASS)
public class SpringBootInitialLoadIntegrationTest { 
// ...  
}

AFTER_TEST_CLASS 则用于测试后清理:

@Sql(scripts = {"/delete_employees_data.sql"}, 
     executionPhase = AFTER_TEST_CLASS)
public class SpringBootInitialLoadIntegrationTest {
// ...
}

⚠️ 注意:类级配置不能被方法级覆盖,而是追加执行。

6.2. 方法级别 @Sql 注解

为特定测试方法加载数据:

@Test
@Sql({"/import_senior_employees.sql"})
public void testLoadDataForTestCase() {
    assertEquals(5, employeeRepository.findAll().size());
}

同样可指定执行阶段(BEFORE_TEST_METHOD/AFTER_TEST_METHOD):

@Test
@Sql(scripts = {"/import_senior_employees.sql"}, 
     executionPhase = BEFORE_TEST_METHOD)
public void testLoadDataForTestCase() {
    assertEquals(5, employeeRepository.findAll().size());
}

默认情况下方法级 @Sql 会覆盖类级声明

@Sql(scripts = {"/employees_schema.sql", "/import_employees.sql"})
public class SpringBootInitialLoadIntegrationTest {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Test 
    @Sql(scripts = {"/import_senior_employees.sql"})
    public void testLoadDataForTestClass() {
        assertEquals(5, employeeRepository.findAll().size()); // 只执行了 import_senior_employees.sql
   }
}

需要合并?用 @SqlMergeMode 注解解决。

7. @SqlConfig

@SqlConfig 配置 SQL 脚本的解析和执行方式。可在类级别(全局配置)或特定 @Sql 注解上使用。

示例:指定脚本编码和事务模式:

@Test
@Sql(scripts = {"/import_senior_employees.sql"}, 
  config = @SqlConfig(encoding = "utf-8", transactionMode = TransactionMode.ISOLATED))
public void testLoadDataForTestCase() {
    assertEquals(5, employeeRepository.findAll().size());
}

主要配置项:

属性 作用
blockCommentStartDelimiter 块注释起始标识符
blockCommentEndDelimiter 块注释结束标识符
commentPrefix 单行注释前缀
dataSource 目标数据源 Bean 名称
encoding 脚本文件编码(默认平台编码)
errorMode 错误处理模式
separator 语句分隔符(默认 "--")
transactionManager 事务管理器 Bean 名称
transactionMode 脚本执行的事务模式

8. @SqlGroup

Java 8+ 支持重复注解,可直接用多个 @Sql。Java 7 及以下需用容器注解 @SqlGroup

@SqlGroup({
  @Sql(scripts = "/employees_schema.sql", 
    config = @SqlConfig(transactionMode = TransactionMode.ISOLATED)),
  @Sql("/import_employees.sql")})
public class SpringBootSqlGroupAnnotationIntegrationTest {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Test
    public void testLoadDataForTestCase() {
        assertEquals(3, employeeRepository.findAll().size());
    }
}

9. 总结

本文我们学习了如何使用 schema.sqldata.sql 初始化数据库结构及数据,以及如何用 @Sql@SqlConfig@SqlGroup 为测试加载数据。

⚠️ 重要提醒:这些方案更适合简单场景。复杂项目建议使用专业工具如 LiquibaseFlyway

完整代码示例可在 GitHub 获取。


原始标题:Guide on Loading Initial Data with Spring Boot

« 上一篇: OSGi 介绍
» 下一篇: Java Weekly, 第204期