1. 概述
Spring Boot 让管理数据库变更变得异常简单。使用默认配置时,它会自动扫描项目中的实体类并创建对应的数据表。
但有时我们需要更精细的控制,这时就能用上 Spring 的 data.sql
和 schema.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
),可能会产生冲突。解决方法:
- 完全禁用 Hibernate 的 DDL 功能:
spring.jpa.hibernate.ddl-auto=none
- 或者想同时使用两者时:
这样执行顺序就变成了:Hibernate 建表 →spring.jpa.defer-datasource-initialization=true
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.sql
和 data.sql
初始化数据库结构及数据,以及如何用 @Sql
、@SqlConfig
和 @SqlGroup
为测试加载数据。
⚠️ 重要提醒:这些方案更适合简单场景。复杂项目建议使用专业工具如 Liquibase 或 Flyway。
完整代码示例可在 GitHub 获取。