spring.sql.init 使用注意事项

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

默认情况下,每次 Spring Boot 项目启动都会自动执行 spring.sql.init 配置的 SQL 脚本 —— 但前提是满足初始化条件


关键结论:是否每次启动都执行?

条件是否执行?说明
spring.sql.init.mode=always每次启动都执行,无论数据库是否存在或是否已初始化
spring.sql.init.mode=embedded仅当使用嵌入式数据库时执行如 H2、HSQL、Derby 等。非嵌入式数据库(如 MySQL、PostgreSQL)不执行
spring.sql.init.mode=never从不执行完全禁用初始化功能
数据库中已有表结构,且脚本中有 CREATE TABLE IF NOT EXISTS仍会执行脚本仍会被运行,但不会报错(因为用了 IF NOT EXISTS
数据库中已有数据,脚本中有 INSERT仍会执行重复插入数据,可能导致主键冲突或数据冗余!

🔥 重点提醒
Spring Boot 的 spring.sql.init 是“无状态”的初始化机制 —— 它不会判断“是否已经初始化过”,它只认配置和脚本文件的存在!


📌 示例说明

场景 1:mode: always + 启动两次
spring:
  sql:
    init:
      mode: always
      schema-locations: classpath:schema-mysql.sql
      data-locations: classpath:data-mysql.sql
  • 第一次启动:
    • 执行 schema-mysql.sql → 创建 users
    • 执行 data-mysql.sql → 插入 3 条用户记录
  • 第二次启动:
    • 再次执行 schema-mysql.sql → 如果没有 IF NOT EXISTS,会报错(表已存在)
    • 再次执行 data-mysql.sql再次插入 3 条相同数据 → 可能违反唯一约束(如 username UNIQUE

👉 结果:数据重复、主键冲突、应用启动失败!


✅ 如何避免重复执行导致的问题?

✅ 方法一:在 SQL 脚本中使用安全语法(推荐)
-- schema-mysql.sql
CREATE TABLE IF NOT EXISTS users ( ... );  -- 安全建表
CREATE TABLE IF NOT EXISTS roles ( ... );

-- data-mysql.sql
INSERT INTO users (username, email) 
SELECT 'admin', 'admin@example.com' 
WHERE NOT EXISTS (SELECT 1 FROM users WHERE username = 'admin');

✅ 使用 IF NOT EXISTSWHERE NOT EXISTS 可以幂等执行(多次执行也不会出错或重复)

✅ 方法二:开发阶段用 mode: always,生产环境设为 never
# application-dev.yml(开发环境)
spring:
  sql:
    init:
      mode: always

# application-prod.yml(生产环境)
spring:
  sql:
    init:
      mode: never
✅ 方法三:改用 Flyway / Liquibase(生产推荐)

这两个工具会记录每个迁移脚本的执行状态(在 flyway_schema_history 表中),确保:

  • 每个脚本只执行一次
  • 支持版本控制、回滚、顺序执行
  • 适合团队协作和生产部署
<!-- Maven 依赖示例 -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

💡 最佳实践
开发/测试 → 用 spring.sql.init 快速搭建
预生产/生产必须改用 Flyway/Liquibase


🚫 常见错误场景(不加判断直接写 INSERT)

-- data-mysql.sql(错误写法)
INSERT INTO users VALUES (1, 'admin', 'admin@xxx.com');
INSERT INTO users VALUES (2, 'user1', 'user1@xxx.com');

→ 第二次启动时会报错:

Duplicate entry '1' for key 'PRIMARY'

因为你试图插入已存在的主键!


✅ 总结:什么时候会执行?

条件是否执行
mode=always每次启动都执行(危险!需幂等脚本)
mode=embedded仅嵌入式数据库(如 H2)启动时执行
mode=never❌ 从不执行
数据库不存在✅ 执行(会创建)
数据库存在但无表✅ 执行(建表)
数据库存在且有表仍然执行(可能报错或重复)

✅ 最佳建议(强烈推荐)

场景推荐做法
本地开发使用 mode: always + 幂等脚本(IF NOT EXISTS + WHERE NOT EXISTS
单元测试使用 H2 + mode: always,每次测试前干净重建
CI/CD 测试环境使用 mode: always,配合内存数据库(H2)
预生产/生产环境禁用 spring.sql.init,改用 FlywayLiquibase 管理数据库变更

🛠️ 附:如何快速验证是否被执行?

application.yml 中开启日志:

logging:
  level:
    org.springframework.boot.jdbc: DEBUG

启动后你会看到类似日志:

o.s.b.jdbc.init.ScriptStatementRunner : Executing SQL script from class path resource [schema-mysql.sql]
o.s.b.jdbc.init.ScriptStatementRunner : Executed SQL script from class path resource [schema-mysql.sql] in 45 ms.
o.s.b.jdbc.init.ScriptStatementRunner : Executing SQL script from class path resource [data-mysql.sql]
o.s.b.jdbc.init.ScriptStatementRunner : Executed SQL script from class path resource [data-mysql.sql] in 12 ms.

如果看到这些日志,就说明脚本被成功执行了。


一句话总结

spring.sql.init 默认每次启动都执行脚本 —— 你必须自己保证脚本是“幂等”的,否则就会出错!
生产环境请务必改用 Flyway/Liquibase!

root@ser738576325243:~# ps -ef | grep java root 1740766 1740742 0 Jul31 ? 00:00:00 sh -c java -Dreactor.schedulers.defaultBoundedElasticOnVirtualThreads=true ${JVM_OPTS} org.springframework.boot.loader.launch.JarLauncher ${0} ${@} --spring.r2dbc.url=r2dbc:pool:mariadb://halodb1/halo_db1 --spring.r2dbc.username=root --spring.r2dbc.password=Gao010527@ --spring.sql.init.platform=mariadb --halo.external-url=http://127.0.0.1:8090/ root 1740803 1740766 0 Jul31 ? 00:11:46 java -Dreactor.schedulers.defaultBoundedElasticOnVirtualThreads=true -Xmx256m -Xms256m org.springframework.boot.loader.launch.JarLauncher --spring.r2dbc.url=r2dbc:pool:mariadb://halodb1/halo_db1 --spring.r2dbc.username=root --spring.r2dbc.password=Gao010527@ --spring.sql.init.platform=mariadb --halo.external-url=http://127.0.0.1:8090/ root 1741435 1741415 0 Jul31 ? 00:00:00 sh -c java -Dreactor.schedulers.defaultBoundedElasticOnVirtualThreads=true ${JVM_OPTS} org.springframework.boot.loader.launch.JarLauncher ${0} ${@} --spring.r2dbc.url=r2dbc:pool:mariadb://halodb2/halo_db2 --spring.r2dbc.username=root --spring.r2dbc.password=Gao010527@ --spring.sql.init.platform=mariadb --halo.external-url=http://127.0.0.1:8091/ root 1741470 1741435 0 Jul31 ? 00:13:01 java -Dreactor.schedulers.defaultBoundedElasticOnVirtualThreads=true -Xmx256m -Xms256m org.springframework.boot.loader.launch.JarLauncher --spring.r2dbc.url=r2dbc:pool:mariadb://halodb2/halo_db2 --spring.r2dbc.username=root --spring.r2dbc.password=Gao010527@ --spring.sql.init.platform=mariadb --halo.external-url=http://127.0.0.1:8091/ root 3674652 3663214 0 12:03 pts/2 00:00:00 grep --color=auto java 这里好像没有雷池,是两个数据库
08-05
2025-06-20T17:32:56.177+08:00 ERROR 19764 --- [obwiki] [ main] o.s.boot.SpringApplication : Application run failed org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'categoryMapper' defined in file [C:\Users\���DZ�\IdeaProjects\obwiki\target\classes\com\gec\obwiki\mapper\CategoryMapper.class]: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:864) ~[spring-beans-6.2.7.jar:6.2.7] at org.springframework.beans.factory.support.AbstractBeanFactory.getType(AbstractBeanFactory.java:745) ~[spring-beans-6.2.7.jar:6.2.7] at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:817) ~[spring-beans-6.2.7.jar:6.2.7] at org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector.detect(AnnotationDependsOnDatabaseInitializationDetector.java:36) ~[spring-boot-3.5.0.jar:3.5.0] at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.detectDependsOnInitializationBeanNames(DatabaseInitializationDependencyConfigurer.java:152) ~[spring-boot-3.5.0.jar:3.5.0] at org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor.postProcessBeanFactory(DatabaseInitializationDependencyConfigurer.java:115) ~[spring-boot-3.5.0.jar:3.5.0] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:363) ~[spring-context-6.2.7.jar:6.2.7] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:197) ~[spring-context-6.2.7.jar:6.2.7] at org.spr
06-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值