数据初始化
本教程共 48 篇 · 第 30 篇 · 更新于 2026-08-13 · 约 6 分钟阅读
本节目标:会用 schema.sql 建表、data.sql 灌数据,搞清楚初始化模式和 Hibernate 建表的先后顺序,知道生产环境怎么避坑。
初始化脚本是干什么的
应用启动时自动执行 SQL 脚本,建表、造数据,省得每次手动操作数据库。这套机制叫 Spring SQL 初始化,对应的就是 classpath 下的两个文件:
schema.sql:放建表语句,定义结构。data.sql:放插入语句,填充数据。
都放在 src/main/resources/ 下,Spring Boot 启动时自动发现并执行。执行顺序固定:schema.sql 先跑,data.sql 后跑。结构在前,数据在后,别反。
最简单的例子
schema.sql:
create table product (
id bigint auto_increment primary key,
name varchar(100) not null,
price decimal(10, 2)
);
data.sql:
insert into product (name, price) values ('机械键盘', 399.00);
insert into product (name, price) values ('显示器', 1299.00);
加上 H2 依赖和 JDBC starter,启动后表和数据就都齐了。
完整的最小配置串一遍(H2 + JPA + 脚本初始化):
spring:
datasource:
url: "jdbc:h2:mem:testdb"
sql:
init:
mode: always # 内存库每次启动都要重新初始化
jpa:
hibernate:
ddl-auto: none # 结构交给 schema.sql
defer-datasource-initialization: true # 确保脚本在实体工厂就绪后执行
这套组合是开发环境的经典搭配:启动即得一张带数据的表,关掉全消失,下次启动再来一遍。
这套机制只负责「初始化」,不负责「演进」。结构要变,还是得上 Flyway(上一节的内容)。
Note脚本默认用
;分隔语句。脚本里要自定义分隔符(比如存储过程里的;),用spring.sql.init.separator改。中文内容乱码时,检查spring.sql.init.encoding: UTF-8是否设置。
脚本报错会怎样?直接导致启动失败。日志里能看到具体哪条 SQL 出错,这是好事——初始化失败就该失败得响亮,别让应用带着残缺的表跑起来。
还有个隐患:脚本不是幂等的。data.sql 里两条相同的 INSERT,第二次启动就主键冲突。脚本里多写防御性语句,比如建表用 create table if not exists,插入前先判断,开发期能省不少折腾。
初始化模式:什么时候执行
脚本不是每个环境都执行。用 spring.sql.init.mode 控制,三个取值:
spring:
sql:
init:
mode: always # 总是执行(非嵌入式数据库要用这个)
| 取值 | 行为 |
|---|---|
embedded | 只用嵌入式数据库时执行(默认值) |
always | 任何数据库都执行 |
never | 从不执行 |
默认是 embedded。所以 H2 开发时不用配,一切正常;换成 MySQL 后脚本突然不跑了,多半就是忘了设 always。
NoteSpring Boot 2.5 之前这个属性叫
spring.datasource.initialization-mode,已废弃。教程里看到旧名字,直接换成spring.sql.init.mode。
生产环境默认要设 never:线上库的脚本一旦可重复执行,等于把删表风险交给启动流程。开发用 always 图方便,生产用 never 图安全,环境不同配置不同。
结合第 13 章的 Profile,这套差异可以自动切换:dev 环境 always,prod 环境 never,一份代码两套行为,部署时不用改文件。
和 Hibernate 建表打架怎么办
用 JPA 时有个顺序问题:data.sql 往表里插数据,但表还没建——Hibernate 的表还没创建,脚本就先执行了,直接报「表不存在」。
解决办法是把数据源初始化推迟到 Hibernate 建表之后:
spring:
jpa:
defer-datasource-initialization: true
执行顺序变成:Hibernate 按实体建表 → schema.sql 补结构 → data.sql 灌数据。
另一个思路:不用 Hibernate 建表,结构全交给脚本。把 ddl-auto 关掉:
spring:
jpa:
hibernate:
ddl-auto: none
推荐组合整理成一张表,照着选:
| 场景 | ddl-auto | schema.sql | data.sql |
|---|---|---|---|
| 快速原型 | update | 不用 | 不用 |
| 脚本管结构 | none | 建表 | 灌数据 |
| Hibernate 建表+脚本补数据 | update/create-drop | 不用 | 用 + defer |
| 生产(有 Flyway) | none/validate | 不用 | 不用 |
Warning脚本建表和 Hibernate 建表同时开容易出问题:两边都想创建同一张表,可能重复建表报错。二选一:要么
ddl-auto: none全交给脚本,要么脚本只负责数据、结构交给 Hibernate。混合用必须开defer-datasource-initialization且小心重复。
ddl-auto 五种取值复习
五种取值(create、create-drop、update、validate、none)的完整说明在第 27 章讲过,这里不再重复,只留与脚本初始化相关的取舍:结构交给脚本管就用 none;Hibernate 建表、脚本只补数据,就按前面的场景表配 update/create-drop 加 defer-datasource-initialization。
生产环境两种推荐组合:validate + Flyway(Flyway 管结构),或者 none + 手工管理结构。update 上生产是高危操作,它不会删列,但可能加出意料之外的列。
测试环境的 @Sql 注解
测试里不想动全局的 schema.sql,可以用 @Sql 注解按测试类/方法加载脚本:
import org.springframework.test.context.jdbc.Sql;
@SpringBootTest
@Sql(scripts = "/test-data.sql")
class ProductRepositoryTest {
// 每个测试方法执行前,先跑 /test-data.sql
}
/test-data.sql 放在 src/test/resources/ 下,只影响这个测试类,不污染主数据。这是测试数据初始化的标准姿势。
@Sql 的执行时机用 executionPhase 控制:默认 BEFORE_TEST_METHOD(每个测试方法前跑),还有 AFTER_TEST_METHOD(测试方法后跑)、BEFORE_TEST_CLASS(测试类前跑,Spring 6.1 / Boot 3.2 起支持):
import org.springframework.test.context.jdbc.Sql;
@SpringBootTest
@Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
class ProductRepositoryTest {
// 每个测试方法跑完,执行清理脚本
}
测试数据各测各的,互不污染,是集成测试的常见配合。
H2 控制台:开发时看数据
H2 自带网页控制台,改两行配置就能用:
spring:
h2:
console:
enabled: true # 只建议开发环境开
启动后访问 /h2-console,JDBC URL 填 jdbc:h2:mem:testdb(默认内存库名),就能在浏览器里看表、跑 SQL。
控制台路径也能改,spring.h2.console.path: /h2,按团队习惯来。Console 只在本机访问没问题,部署到服务器后要加访问控制,不然数据库等于裸奔。
WarningH2 控制台是开发工具,没有安全防护,生产环境千万别开。Spring Security 项目里还要额外放行
/h2-console路径,否则被登录拦截。
与 Flyway 的执行顺序
项目里同时有 Flyway 和 SQL 初始化时,顺序是固定的:Flyway 先执行,SQL 初始化后执行。
所以常见搭配是:结构交给 Flyway 的版本化脚本,data.sql 只放开发环境的种子数据(比如字典表、测试账号)。这样两者各司其职,不冲突。
这个顺序写死在自动配置里,不可配置,记住「Flyway 在前、SQL 初始化在后」即可。
小结
schema.sql 建表、data.sql 灌数据,spring.sql.init.mode 控制执行时机。和 JPA 一起用,要么 ddl-auto: none 让脚本全管,要么 defer-datasource-initialization 推迟脚本。生产环境关掉脚本初始化,结构交给 Flyway。
下一节进入缓存:@Cacheable 注解和 Redis 集成。