Spring Boot: database initialization fails with "Referential integrity constraint violation" on init SQL scripts

huangapple go评论72阅读模式
英文:

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

这是非常常见的做法。

从图形上看,数据库模式如下:

Spring Boot: database initialization fails with "Referential integrity constraint violation" on init SQL scripts

如果我手动运行上述脚本(按照"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');

结果是ingredienttaco表格填充,而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:

Spring Boot: database initialization fails with "Referential integrity constraint violation" on init SQL scripts

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 中,// 注释在某种程度上是被允许的:

Spring Boot: database initialization fails with "Referential integrity constraint violation" on init SQL scripts

"> 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:

H2 comments in SQL

Spring Boot: database initialization fails with "Referential integrity constraint violation" on init SQL scripts

> 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.

huangapple
  • 本文由 发表于 2023年7月27日 21:43:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76780382.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定