Appearance
Spring Boot 数据库初始化指南 🚀
概述
在现代应用开发中,数据库初始化是一个至关重要的环节。想象一下,如果每次部署新应用时都需要手动创建表结构、插入基础数据,那将是多么繁琐和容易出错的过程!Spring Boot 为我们提供了多种优雅的数据库初始化方案,让这个过程变得自动化、可靠且易于管理。
NOTE
数据库初始化不仅仅是创建表结构,还包括插入初始数据、执行数据迁移等操作。选择合适的初始化策略对项目的长期维护至关重要。
为什么需要数据库初始化?
在没有自动化数据库初始化之前,开发团队通常面临以下痛点:
- 环境不一致:开发、测试、生产环境的数据库结构可能存在差异
- 部署复杂:每次部署都需要手动执行 SQL 脚本
- 版本管理困难:数据库结构变更难以追踪和回滚
- 团队协作问题:新成员加入项目时,环境搭建复杂
Spring Boot 的数据库初始化机制完美解决了这些问题! 🎉
初始化方式对比
方式一:使用 Hibernate 自动初始化
核心配置
Hibernate 提供了 ddl-auto
属性来控制数据库初始化行为:
kotlin
spring:
jpa:
hibernate:
ddl-auto: create-drop # 每次启动重新创建表
show-sql: true # 显示 SQL 语句
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
kotlin
spring:
jpa:
hibernate:
ddl-auto: validate # 仅验证表结构,不修改
show-sql: false
datasource:
url: jdbc:mysql://localhost:3306/myapp
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
DDL-Auto 选项详解
选项 | 说明 | 适用场景 |
---|---|---|
none | 不执行任何操作 | 生产环境,使用专业迁移工具 |
validate | 验证表结构是否匹配 | 生产环境验证 |
update | 更新表结构(不删除) | 开发环境 ⚠️ |
create | 每次启动创建表 | 测试环境 |
create-drop | 启动创建,关闭删除 | 单元测试 |
WARNING
在生产环境中,永远不要使用 create
、create-drop
或 update
!这可能导致数据丢失。
实体类示例
kotlin
@Entity
@Table(name = "users")
data class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
@Column(nullable = false, unique = true)
val username: String,
@Column(nullable = false)
val email: String,
@CreationTimestamp
val createdAt: LocalDateTime = LocalDateTime.now()
)
@Entity
@Table(name = "posts")
data class Post(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
@Column(nullable = false)
val title: String,
@Column(columnDefinition = "TEXT")
val content: String,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
val author: User
)
初始数据导入
当使用 create
或 create-drop
时,可以在 src/main/resources/import.sql
中添加初始数据:
sql
-- import.sql
INSERT INTO users (username, email) VALUES ('admin', '[email protected]');
INSERT INTO users (username, email) VALUES ('user1', '[email protected]');
INSERT INTO posts (title, content, user_id) VALUES ('Welcome', 'Welcome to our blog!', 1);
TIP
import.sql
是 Hibernate 特有的功能,与 Spring Boot 无关。它只在表结构从零创建时执行。
方式二:使用 SQL 脚本初始化
基本配置
Spring Boot 会自动寻找并执行以下脚本:
kotlin
// application.yml
spring:
sql:
init:
mode: always # 总是执行初始化
schema-locations: # 表结构脚本位置
- classpath:schema.sql
- classpath:schema-${spring.sql.init.platform}.sql
data-locations: # 数据脚本位置
- classpath:data.sql
- classpath:data-${spring.sql.init.platform}.sql
platform: mysql # 数据库平台
continue-on-error: false # 遇到错误是否继续
脚本文件结构
src/main/resources/
├── schema.sql # 通用表结构
├── schema-mysql.sql # MySQL 特定表结构
├── schema-postgresql.sql # PostgreSQL 特定表结构
├── data.sql # 通用初始数据
├── data-mysql.sql # MySQL 特定数据
└── data-postgresql.sql # PostgreSQL 特定数据
脚本示例
sql
-- 用户表
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 文章表
CREATE TABLE IF NOT EXISTS posts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT,
user_id BIGINT NOT NULL,
status VARCHAR(20) DEFAULT 'DRAFT',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 创建索引
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_status ON posts(status);
sql
-- 插入管理员用户
INSERT INTO users (username, email, password) VALUES
('admin', '[email protected]', '$2a$10$...');
-- 插入示例文章
INSERT INTO posts (title, content, user_id, status) VALUES
('欢迎使用我们的博客系统', '这是一篇欢迎文章...', 1, 'PUBLISHED'),
('Spring Boot 入门指南', 'Spring Boot 是一个...', 1, 'DRAFT');
与 JPA 结合使用
当同时使用 JPA 和 SQL 脚本时,需要注意执行顺序:
kotlin
// application.yml
spring:
jpa:
defer-datasource-initialization: true # 延迟数据源初始化 [!code highlight]
hibernate:
ddl-auto: create
sql:
init:
mode: always
IMPORTANT
设置 defer-datasource-initialization: true
确保 SQL 脚本在 Hibernate 创建表结构之后执行。
方式三:使用 Flyway 数据库迁移
为什么选择 Flyway?
Flyway 是一个专业的数据库迁移工具,它解决了以下问题:
- 版本控制:每个迁移都有版本号,可追踪数据库变更历史
- 增量更新:只执行未应用的迁移,避免重复执行
- 回滚支持:支持数据库版本回滚(商业版)
- 多环境支持:不同环境可以有不同的迁移策略
添加依赖
kotlin
// build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-mysql") // 根据数据库选择
}
配置 Flyway
kotlin
// application.yml
spring:
flyway:
enabled: true
locations:
- classpath:db/migration
- classpath:db/migration/{vendor} # 数据库特定迁移
baseline-on-migrate: true # 在非空数据库上启用基线
validate-on-migrate: true # 验证迁移脚本
out-of-order: false # 不允许乱序执行
迁移脚本命名规范
src/main/resources/db/migration/
├── V1__Create_user_table.sql
├── V2__Create_post_table.sql
├── V3__Add_user_avatar_column.sql
├── V4__Insert_initial_data.sql
└── V5__Create_comment_table.sql
NOTE
Flyway 迁移脚本命名规则:V{版本号}__{描述}.sql
,版本号用下划线分隔(如 V2_1)。
迁移脚本示例
sql
-- 创建用户表
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
status VARCHAR(20) DEFAULT 'ACTIVE',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_status ON users(status);
sql
-- 创建文章表
CREATE TABLE posts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
slug VARCHAR(200) NOT NULL UNIQUE,
content TEXT,
excerpt VARCHAR(500),
user_id BIGINT NOT NULL,
status VARCHAR(20) DEFAULT 'DRAFT',
published_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_posts_user FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 创建索引
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_status ON posts(status);
CREATE INDEX idx_posts_published_at ON posts(published_at);
sql
-- 为用户表添加头像字段
ALTER TABLE users
ADD COLUMN avatar_url VARCHAR(255) NULL AFTER email;
-- 为现有用户设置默认头像
UPDATE users
SET avatar_url = 'https://example.com/default-avatar.png'
WHERE avatar_url IS NULL;
Java 迁移示例
除了 SQL 脚本,Flyway 还支持 Java 迁移:
kotlin
@Component
class V4__InsertInitialData : JavaMigration {
override fun getVersion(): MigrationVersion = MigrationVersion.fromVersion("4")
override fun getDescription(): String = "Insert initial data"
override fun migrate(context: Context) {
val jdbcTemplate = JdbcTemplate(context.connection)
// 插入管理员用户
jdbcTemplate.update("""
INSERT INTO users (username, email, password, status)
VALUES (?, ?, ?, ?)
""".trimIndent(),
"admin",
"[email protected]",
passwordEncoder.encode("admin123"),
"ACTIVE"
)
// 插入示例文章
jdbcTemplate.update("""
INSERT INTO posts (title, slug, content, user_id, status, published_at)
VALUES (?, ?, ?, ?, ?, ?)
""".trimIndent(),
"欢迎使用博客系统",
"welcome-to-blog",
"这是我们博客系统的第一篇文章...",
1,
"PUBLISHED",
Timestamp.from(Instant.now())
)
}
}
环境特定迁移
kotlin
spring:
flyway:
locations:
- classpath:db/migration
- classpath:db/migration/dev # 开发环境特定迁移
sql
-- 开发环境测试数据
INSERT INTO users (username, email, password, status) VALUES
('testuser1', '[email protected]', '$2a$10$...', 'ACTIVE'),
('testuser2', '[email protected]', '$2a$10$...', 'ACTIVE');
INSERT INTO posts (title, slug, content, user_id, status, published_at) VALUES
('测试文章1', 'test-post-1', '这是测试文章内容...', 2, 'PUBLISHED', NOW()),
('测试文章2', 'test-post-2', '这是另一篇测试文章...', 3, 'DRAFT', NULL);
方式四:使用 Liquibase 数据库迁移
Liquibase vs Flyway
特性 | Flyway | Liquibase |
---|---|---|
脚本格式 | SQL、Java | XML、YAML、JSON、SQL |
学习曲线 | 简单 | 中等 |
回滚支持 | 商业版 | 开源版支持 |
数据库抽象 | 有限 | 强大 |
条件执行 | 有限 | 丰富 |
添加依赖
kotlin
// build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.liquibase:liquibase-core")
}
配置 Liquibase
kotlin
// application.yml
spring:
liquibase:
change-log: classpath:db/changelog/db.changelog-master.yaml
contexts: dev,prod # 执行上下文
default-schema: myapp # 默认模式
drop-first: false # 不要在生产环境设为 true!
主变更日志
yaml
# db/changelog/db.changelog-master.yaml
databaseChangeLog:
- include:
file: db/changelog/v1.0/db.changelog-v1.0.yaml
- include:
file: db/changelog/v1.1/db.changelog-v1.1.yaml
- include:
file: db/changelog/v2.0/db.changelog-v2.0.yaml
变更集示例
yaml
databaseChangeLog:
- changeSet:
id: create-users-table
author: developer
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: username
type: VARCHAR(50)
constraints:
nullable: false
unique: true
- column:
name: email
type: VARCHAR(100)
constraints:
nullable: false
unique: true
- column:
name: password
type: VARCHAR(255)
constraints:
nullable: false
- column:
name: created_at
type: TIMESTAMP
defaultValueComputed: CURRENT_TIMESTAMP
- changeSet:
id: create-posts-table
author: developer
changes:
- createTable:
tableName: posts
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: title
type: VARCHAR(200)
constraints:
nullable: false
- column:
name: content
type: TEXT
- column:
name: user_id
type: BIGINT
constraints:
nullable: false
foreignKeyName: fk_posts_user
references: users(id)
yaml
databaseChangeLog:
- changeSet:
id: add-user-avatar-column
author: developer
changes:
- addColumn:
tableName: users
columns:
- column:
name: avatar_url
type: VARCHAR(255)
afterColumn: email
- changeSet:
id: insert-initial-admin
author: developer
context: prod
changes:
- insert:
tableName: users
columns:
- column:
name: username
value: admin
- column:
name: email
value: [email protected]
- column:
name: password
value: $2a$10$encrypted_password_here
条件执行和回滚
yaml
databaseChangeLog:
- changeSet:
id: add-index-if-not-exists
author: developer
preConditions:
- not:
- indexExists:
indexName: idx_posts_title
changes:
- createIndex:
indexName: idx_posts_title
tableName: posts
columns:
- column:
name: title
rollback:
- dropIndex:
indexName: idx_posts_title
tableName: posts
- changeSet:
id: update-user-status
author: developer
context: migration
changes:
- update:
tableName: users
columns:
- column:
name: status
value: ACTIVE
where: status IS NULL
测试环境的特殊处理
Flyway 测试迁移
src/test/resources/db/migration/
└── V9999__test-data.sql # 高版本号确保最后执行
sql
-- V9999__test-data.sql
-- 测试用户数据
INSERT INTO users (username, email, password, status) VALUES
('testuser', '[email protected]', 'test123', 'ACTIVE');
-- 测试文章数据
INSERT INTO posts (title, slug, content, user_id, status) VALUES
('测试文章', 'test-article', '这是测试内容', 1, 'PUBLISHED');
Liquibase 测试配置
yaml
spring:
liquibase:
change-log: classpath:db/changelog/db.changelog-test.yaml
contexts: test
yaml
databaseChangeLog:
- include:
file: classpath:db/changelog/db.changelog-master.yaml
- changeSet:
runOrder: last
id: insert-test-data
author: test
context: test
changes:
- insert:
tableName: users
columns:
- column:
name: username
value: testuser
- column:
name: email
value: [email protected]
kotlin
@SpringBootTest
@ActiveProfiles("test")
@Transactional
class UserServiceTest {
@Autowired
private lateinit var userService: UserService
@Test
fun `should find test user`() {
val user = userService.findByUsername("testuser")
assertThat(user).isNotNull
assertThat(user?.email).isEqualTo("[email protected]")
}
}
依赖管理和启动顺序
Spring Boot 自动检测数据库初始化器并管理依赖关系:
自定义依赖检测
kotlin
@Component
class CustomDatabaseInitializer : DatabaseInitializer {
override fun initializeDatabase() {
// 自定义初始化逻辑
log.info("执行自定义数据库初始化...")
}
}
@Service
@DependsOnDatabaseInitialization
class UserService(
private val userRepository: UserRepository
) {
@PostConstruct
fun init() {
// 这个方法会在数据库初始化完成后执行
log.info("UserService 初始化完成,数据库已就绪")
}
}
最佳实践建议
1. 选择合适的初始化策略
推荐策略
- 开发环境:Hibernate DDL + SQL 脚本,快速迭代
- 测试环境:Flyway/Liquibase,确保环境一致性
- 生产环境:专业迁移工具(Flyway/Liquibase),严格版本控制
2. 避免混合使用
WARNING
不要同时使用多种初始化方式!选择一种并坚持使用。
3. 版本控制策略
kotlin
// 推荐的版本号策略
V1_0_0__Initial_schema.sql // 主版本
V1_0_1__Add_user_avatar.sql // 小版本
V1_1_0__Add_comment_feature.sql // 功能版本
V2_0_0__Major_refactoring.sql // 重大重构
4. 环境隔离
kotlin
// application-dev.yml
spring:
jpa:
hibernate:
ddl-auto: create-drop
sql:
init:
mode: always
// application-prod.yml
spring:
jpa:
hibernate:
ddl-auto: validate # [!code highlight]
flyway:
enabled: true
validate-on-migrate: true
5. 备份和回滚策略
IMPORTANT
在生产环境执行数据库迁移前,务必进行数据备份!
bash
# 生产部署前的检查清单
□ 数据库备份已完成
□ 迁移脚本已在测试环境验证
□ 回滚方案已准备
□ 监控和告警已配置
总结
Spring Boot 的数据库初始化机制为我们提供了从简单到复杂的多种选择:
- Hibernate DDL:适合快速原型和开发环境
- SQL 脚本:简单直接,适合小型项目
- Flyway:专业迁移工具,适合大多数项目
- Liquibase:功能丰富,适合复杂场景
选择合适的策略,建立规范的流程,你的数据库管理将变得轻松而可靠! ✨
TIP
记住:好的数据库初始化策略不仅能提高开发效率,更能保障生产环境的稳定性。投入时间建立完善的数据库版本管理体系,绝对是值得的!