配置spring.jpa.hibernate.ddl-auto=update后,如何阻止Spring Boot二次启动执行data.sql?
解决Spring Boot二次启动时data.sql重复执行导致主键冲突的问题
这个问题我碰到好多次了——用Hibernate的update模式自动建表,再加上data.sql初始化数据,首次启动一切顺利,结果第二次启动就因为主键重复炸锅。给你几个实用的解决思路,按需选就行:
方案1:用Spring Boot 2.5+的原生配置控制脚本执行
从Spring Boot 2.5版本开始,官方专门加了控制SQL脚本执行的配置,能轻松解决这个问题:
- 先让Hibernate完成表创建后再执行
data.sql,避免脚本执行时表不存在的问题,添加:
spring.jpa.defer-datasource-initialization=true
- 然后根据你的场景选配置:
- 如果是开发用嵌入式数据库(比如H2),生产用外部MySQL,只想让嵌入式数据库执行
data.sql:
spring.sql.init.mode=embedded- 如果希望脚本永远只执行一次,可以结合自定义判断,或者直接用下面的脚本修改方案更简单。
- 如果是开发用嵌入式数据库(比如H2),生产用外部MySQL,只想让嵌入式数据库执行
方案2:修改data.sql脚本,让它具备幂等性
最直接的办法是给插入语句加MySQL特有的语法,让重复执行时不会触发主键冲突:
- 用
INSERT IGNORE:如果主键已存在,直接忽略这条插入操作
INSERT IGNORE INTO your_table (id, name) VALUES (1, '测试数据');
- 用
ON DUPLICATE KEY UPDATE:如果主键已存在,就更新对应字段(适合需要同步数据的场景)
INSERT INTO your_table (id, name) VALUES (1, '测试数据') ON DUPLICATE KEY UPDATE name = VALUES(name);
这个方案不用改任何Spring配置,脚本本身就能适应重复执行的场景,非常省心。
方案3:自定义初始化逻辑,精准控制
如果需要更灵活的控制(比如判断表中有没有数据再执行插入),可以写个简单的配置类,用JdbcTemplate来处理:
import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; @Configuration public class CustomDataInitializer { @Bean public CommandLineRunner initDatabase(JdbcTemplate jdbcTemplate) { return args -> { // 先查询目标表是否已有数据 Long dataCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM your_table", Long.class); if (dataCount == 0) { // 没有数据时才执行插入 jdbcTemplate.execute("INSERT INTO your_table (id, name) VALUES (1, '测试数据')"); // 这里可以加更多初始化语句 } }; } }
这种方式完全由你掌控初始化时机和条件,适合复杂的初始化场景,而且可以直接删掉data.sql,避免自动执行的问题。
方案4:调整DDL模式配合脚本执行
如果你的表结构已经稳定,不需要Hibernate自动更新表结构了,可以把spring.jpa.hibernate.ddl-auto改成none,然后用schema.sql来管理表结构,data.sql初始化数据,同时添加:
spring.sql.init.mode=once
不过这个方案适合表结构不再变动的场景,因为ddl-auto=none后Hibernate不会再自动创建或更新表。
内容的提问来源于stack exchange,提问作者B G Nithin Malathesh
相关产品推荐
相关产品推荐

