数据初始化
Laravel 用 Seeder(
DatabaseSeeder)给表灌测试/初始数据,跑php artisan db:seed;Spring Boot 没有「Seeder 命令」,常用三种方式:CommandLineRunner(启动后执行一段代码)、SQL 文件初始化(data.sql)、Flyway 种子数据(推荐生产用)。这篇讲清楚分别什么时候用。
方式一:CommandLineRunner(最灵活,类 Seeder)
一个实现 ApplicationRunner / CommandLineRunner 的 @Component,应用启动完成后执行:
java
@Component
public class DataInitializer implements ApplicationRunner {
private final UserRepository userRepository;
private final PasswordEncoder encoder;
public DataInitializer(UserRepository userRepository, PasswordEncoder encoder) {
this.userRepository = userRepository;
this.encoder = encoder;
}
@Override
public void run(ApplicationArguments args) {
if (userRepository.count() > 0) {
return; // 已有数据就不重复灌
}
User admin = new User();
admin.setUsername("admin");
admin.setPassword(encoder.encode("admin123"));
userRepository.save(admin);
System.out.println("已初始化管理员账号");
}
}对照 Laravel
| Laravel Seeder | CommandLineRunner |
|---|---|
php artisan db:seed | 应用启动自动执行 |
factory()->count(10)->create() | 手动循环 save |
if (User::exists()) return; | if (userRepository.count() > 0) return; |
这是最常用的方式,能写完整业务逻辑(查库、判重、加密),推荐优先掌握。
方式二:data.sql / schema.sql(纯 SQL 初始化)
Spring Boot 支持启动时执行 src/main/resources/ 下的 SQL 文件:
| 文件 | 作用 |
|---|---|
schema.sql | 建表(一般不用,表结构交给 Flyway) |
data.sql | 插入数据 |
sql
-- src/main/resources/data.sql
INSERT INTO roles (id, name) VALUES (1, 'admin');
INSERT INTO roles (id, name) VALUES (2, 'user');yaml
spring:
sql:
init:
mode: always # 每次启动都执行(默认 embedded 时执行)data.sql 的坑
- 配合 JPA:JPA 默认
ddl-auto建表发生在 data.sql 之前,顺序冲突。用spring.jpa.defer-datasource-initialization: true让 JPA 建完表再执行 data.sql mode: always每次启动都跑,INSERT会重复 → 用INSERT IGNORE或只做幂等- 简单插入适合,复杂逻辑(判重、加密)用方式一
方式三:Flyway 种子数据(生产推荐)
生产环境要的种子数据(初始角色、默认配置项),应该走 Flyway 迁移而不是 data.sql,因为迁移有版本控制和幂等保证:
sql
-- db/migration/V2__seed_roles.sql
INSERT INTO roles (id, name, created_at) VALUES
(1, 'admin', NOW()),
(2, 'user', NOW());每次启动 Flyway 只执行一次,天然幂等。
TIP
判断:结构 + 必须存在的初始数据 → Flyway;测试/演示用的假数据 → CommandLineRunner(判断环境再灌)。
只在特定环境灌数据
测试数据不该进生产。用 Profile 控制:
yaml
# application-dev.yml
spring:
sql:
init:
mode: always
# application-prod.yml
spring:
sql:
init:
mode: never或代码里判断 Profile:
java
@Component
public class DemoDataInitializer implements ApplicationRunner {
private final ConfigurableEnvironment env;
public DemoDataInitializer(ConfigurableEnvironment env) {
this.env = env;
}
@Override
public void run(ApplicationArguments args) {
if (!Arrays.asList(env.getActiveProfiles()).contains("dev")) {
return; // 只在 dev 灌演示数据
}
// 灌 10 篇演示帖子...
}
}三种方式怎么选
| 场景 | 用哪个 |
|---|---|
| 启动后跑一段初始化逻辑(查库、判重、加密) | CommandLineRunner |
| 纯 SQL 插入少量固定数据 | data.sql(简单) |
| 生产必有的初始数据(角色、配置项) | Flyway 迁移 |
| 开发环境演示数据 | CommandLineRunner + 判断 Profile |
生产初始化最佳实践
- 角色、权限、枚举等「业务必需数据」→ Flyway 迁移,带版本管理
- 管理员账号等敏感数据 → 启动后脚本 + 环境变量传入密码,别写死在代码里