英文:
Spring Boot: database initialization fails with "Referential integrity constraint violation" on init SQL scripts
问题
我正在尝试对《Spring实战》中的“TacoOrder”应用程序进行一些改进,并从我的Spring Boot应用程序中设置H2数据库的数据库初始化。
这是通过按照使用基本SQL脚本初始化数据库中描述的方式添加两个数据初始化脚本来完成的。
DDL脚本schema-h2.sql
DROP TABLE IF EXISTS ingredient;
DROP TABLE IF EXISTS ingredient_type;
CREATE TABLE IF NOT EXISTS ingredient_type (
name VARCHAR(100) NOT NULL PRIMARY KEY,
index INTEGER NOT NULL UNIQUE,
exclusive BOOLEAN NOT NULL,
mandatory BOOLEAN NOT NULL
);
CREATE TABLE IF NOT EXISTS ingredient (
name VARCHAR(100) NOT NULL PRIMARY KEY, -- "enum-like" name, lower-cased
description CHAR(100) NOT NULL, -- a cleartext description
type VARCHAR(100) NOT NULL,
FOREIGN KEY(type) REFERENCES ingredient_type(name)
);
DML脚本data-h2.sql
INSERT INTO ingredient_type (name, index, exclusive, mandatory)
VALUES ('wrap', 1, true, true),
('protein', 2, false, false),
('veggies', 3, false, false),
('cheese', 4, false, false),
('sauce', 5, false, true);
INSERT INTO ingredient (name, description, type)
VALUES ('flto', 'Flour Tortilla', 'wrap'),
('coto', 'Corn Tortilla', 'wrap'),
('grbf', 'Ground Beef', 'protein'),
('carn', 'Carnitas', 'protein'),
('tmto', 'Diced Tomatoes', 'veggies'),
('letc', 'Lettuce', 'veggies'),
('ched', 'Cheddar', 'cheese'),
('jack', 'Monterrey Jack', 'cheese'),
('slsa', 'Salsa', 'sauce'),
('srcr', 'Sour Cream', 'sauce');
上述内容应该添加到application.yml
中的适当条目中:
spring:
sql:
init:
mode: always # also initialize for non-in-memory databases
platform: h2 # use the script that have "h2" in their name
这是非常常见的做法。
从图形上看,数据库模式如下:
如果我手动运行上述脚本(按照"schema"然后"data"的顺序),一切都正常,所需的预填充表格会出现在H2数据库中。
但如果我启动Spring Boot应用程序,表格会出现在H2数据库中,但数据预填充会因为约束违反而失败:
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: 引用完整性约束违反: "CONSTRAINT_10: PUBLIC.INGREDIENT FOREIGN KEY(TYPE) REFERENCES PUBLIC.INGREDIENT_TYPE(NAME) ('wrap')";
SQL语句:
INSERT INTO ingredient (name, description, type) VALUES
('flto', 'Flour Tortilla', 'wrap'),
('coto', 'Corn Tortilla', 'wrap'),
('grbf', 'Ground Beef', 'protein'), ... [23506-220]
在应用程序日志中,我们看到:
在上下文初始化期间遇到异常 - 取消刷新尝试...
无法执行SQL脚本文件中的语句 #2 [.../data-h2.sql]
我猜测由于某种原因,ingredient_type
表格的填充未完成或被跳过。但为什么呢?
实际上,如果我删除FOREIGN KEY
约束,我最终会得到填充的ingredient
表格和空的ingredient_type
表格。然后应用程序仍然无法运行,因为当然没有数据。
实验 #1
另一次尝试,我添加了第三个表格的定义和数据,同时省略了FOREIGN KEY
约束,如下所示:
CREATE TABLE IF NOT EXISTS taco (
id INTEGER NOT NULL PRIMARY KEY,
when_created DATETIME,
when_updated DATETIME
)
INSERT INTO taco (id, when_created, when_updated)
VALUES (1, '2023-01-01 12:00:00', '2023-01-01 13:00:00');
结果是ingredient
和taco
表格填充,而ingredient_type
未填充。看起来这个表格有一些奇怪的地方?但再次手动运行DDL和DML脚本可以正常工作。
实验 #2
哦,原来是因为Spring Boot只会忽略DML文件中的第一个INSERT
语句。如果将用于填充ingredient
表格的语句移到DML文件的顶部,那么ingredient
表格将保持为空。同样,将用于填充taco
表格的语句移到顶部意味着taco
表格将不会被填充。但为什么呢!
英文:
I'm trying to refine the "TacoOrder" application from "Spring in Action" a bit and am setting up database initialization for a H2 database from my Spring Boot application.
This is done by adding two data initialization scripts as described in Initialize a Database Using Basic SQL Scripts
The DDL script schema-h2.sql
DROP TABLE IF EXISTS ingredient;
DROP TABLE IF EXISTS ingredient_type;
CREATE TABLE IF NOT EXISTS ingredient_type (
name VARCHAR(100) NOT NULL PRIMARY KEY,
index INTEGER NOT NULL UNIQUE,
exclusive BOOLEAN NOT NULL,
mandatory BOOLEAN NOT NULL
);
CREATE TABLE IF NOT EXISTS ingredient (
name VARCHAR(100) NOT NULL PRIMARY KEY, -- "enum-like" name, lower-cased
description CHAR(100) NOT NULL, -- a cleartext description
type VARCHAR(100) NOT NULL,
FOREIGN KEY(type) REFERENCES ingredient_type(name)
);
The DML script data-h2.sql
INSERT INTO ingredient_type (name, index, exclusive, mandatory)
VALUES ('wrap', 1, true, true),
('protein', 2, false, false),
('veggies', 3, false, false),
('cheese', 4, false, false),
('sauce', 5, false, true);
INSERT INTO ingredient (name, description, type)
VALUES ('flto', 'Flour Tortilla', 'wrap'),
('coto', 'Corn Tortilla', 'wrap'),
('grbf', 'Ground Beef', 'protein'),
('carn', 'Carnitas', 'protein'),
('tmto', 'Diced Tomatoes', 'veggies'),
('letc', 'Lettuce', 'veggies'),
('ched', 'Cheddar', 'cheese'),
('jack', 'Monterrey Jack', 'cheese'),
('slsa', 'Salsa', 'sauce'),
('srcr', 'Sour Cream', 'sauce');
The above being introduced by the appropriate entry in application.yml
:
spring:
sql:
init:
mode: always # also initialize for non-in-memory databases
platform: h2 # use the script that have "h2" in their name
This is all very common.
Graphically, the database schema is:
If I run the above scripts manually (in order "schema" then "data", naturally), everything works fine and the desired pre-filled tables appear in the H2 database.
If I start the Spring Boot application, the tables appear in the H2 database but the data prefill fails with a constraint violation:
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential
integrity constraint violation: "CONSTRAINT_10: PUBLIC.INGREDIENT
FOREIGN KEY(TYPE) REFERENCES PUBLIC.INGREDIENT_TYPE(NAME) ('wrap')";
SQL statement:
INSERT INTO ingredient (name, description, type) VALUES
('flto', 'Flour Tortilla', 'wrap'),
('coto', 'Corn Tortilla', 'wrap'),
('grbf', 'Ground Beef', 'protein'), ... [23506-220]
In the application log, we see:
Exception encountered during context initialization - cancelling refresh attempt ...
Failed to execute SQL script statement #2 of file [.../data-h2.sql]
I surmise that for some reason the filling of table ingredient_type
is not done, or skipped. But why?
In fact, if I remove the FOREIGN KEY
constraint, I end up with a filled ìngredient
table and an empty ingredient_type
table. Then the application still doesn't run because there is no data of course.
Experiment #1
Another trial whereby I add the definition and data for a third table, while leaving out the FOREIGN KEY
constraint, like this:
CREATE TABLE IF NOT EXISTS taco (
id INTEGER NOT NULL PRIMARY KEY,
when_created DATETIME,
when_updated DATETIME
)
INSERT INTO taco (id, when_created, when_updated)
VALUES (1, '2023-01-01 12:00:00', '2023-01-01 13:00:00');
...results in tables ingredient
and taco
filled, and ingredient_type
not filled. Looks like there is something peculiar about that table? But again, running the DDL DML scripts manually works fine.
Experiment #2
Aha, it seems just that the first INSERT
in the DML file is being ignored by Spring Boot. If one moves the statement to fill table ingredient
to the top of the DML file, it is table ingredient
that remains empty. Similarly moving the statement to fill table taco
to the top means that taco
gets the silent treatment. But why!
答案1
得分: 0
尝试将以下属性添加到您的配置中:
spring.jpa.hibernate.ddl-auto=none
该属性的默认值为create-drop。如果未明确设置为其他值,这可能会导致在通过SQL脚本进行数据库初始化期间插入的数据被删除。
英文:
Can you try adding the following property to your configuration:
spring.jpa.hibernate.ddl-auto=none
The default value for this property is create-drop. If not explicitly set to another value this can result in data being deleted that has been inserted during DB initialization via SQL scripts.
答案2
得分: 0
只有以下部分需要翻译:
"It turns out that I had a C++-style comment at the top of the DML file:"
如果将此注释删除,Spring Boot 应用程序初始化会选择第一个 INSERT
,否则会丢弃它。
"One may note that //
comments are sort-of allowed in H2:"
可以注意到在 H2 中,//
注释在某种程度上是被允许的:
"> Compatibility-only non-standard syntax is marked in red, don't use it unless you need it for compatibility with other databases or old versions of H2."
"> 仅用于兼容性的非标准语法标记为红色,除非需要与其他数据库或旧版本的 H2 兼容,否则不要使用它。"
"In fact, replacing the //
by /* ... */
("bracketed comment") fixes the problem."
实际上,将 //
替换为 /* ... */
("括号注释")可以解决问题。
"It is possible that running the DML script from the IDE strips the comment before passing it the H2 driver, which would explain why it works in that case."
有可能是从 IDE 运行 DML 脚本时会在传递给 H2 驱动程序之前删除注释,这可以解释为什么在这种情况下能够正常工作。
"Feels vaguely buggy. But where do I complain? Spring? H2?"
感觉有点模糊的错误。但我应该向谁投诉?Spring?H2?
"This is h2-2.2.220 btw."
顺便提一下,这是 h2-2.2.220 版本。
英文:
It turns out that I had a C++-style comment at the top of the DML file:
// convention: "string-based" keys are always fully lowercase!
If one removes this comment, the first INSERT
is picked by the Spring Boot application initialization, otheriwse it is dropped.
One may note that //
comments are sort-of allowed in H2:
> Compatibility-only non-standard syntax is marked in red, don't use it unless you need it for compatibility with other databases or old versions of H2.
In fact, replacing the //
by /* ... */
("bracketed comment") fixes the problem.
It is possible that running the DML script from the IDE strips the comment before passing it the H2 driver, which would explain why it works in that case.
Feels vaguely buggy. But where do I complain? Spring? H2?
This is h2-2.2.220 btw.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论