OneToMany 关系无法用 liquibase 保存在 spring 中
OneToMany relation cannot be saved in spring with liquibase
我正在尝试在 spring 中构建一个数据模型,该模型通过 one-to-many 关系向下级联最多 3 个级别,但我无法使其与 liquibase 脚本一起使用。
我正在使用 spring 引导与 Kotlin 和 liquibase 与 PostgreSQL 数据库。
到目前为止我做了什么:
- 减少代码以仅包含不起作用的部分(见下文)
- 我尝试了@OneToMany 和@JoinTable 以及@JoinColumn,我也尝试了@ManyToMany 来排除@OneToMany 的问题
- 我 运行 没有 liquibase 的相同代码(下面)让 Hibernate/JPA 从模型创建表
- 这确实有效,所以我从这些表中生成了 liquibase 脚本,但它们看起来和我自己的完全一样(键名除外)
- 使用这些模型检索数据有效(如果我直接通过 SQL 插入数据)
老实说我不确定,如果问题出在模型、配置或 liquibase 脚本中,所以我只 post 所有这些。我缺少配置吗?我是否正确配置了级联?我的模型 definitions/liquibase 脚本错了吗?
我在保存 parent 时遇到的异常是:
Hibernate: insert into parent (name) values (?)
2021-12-15 23:29:16.797 WARN 14115 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23502
2021-12-15 23:29:16.798 ERROR 14115 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: null value in column "id" of relation "parent" violates not-null constraint
Detail: Failing row contains (null, Test 1).
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [id" of relation "parent]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "id" of relation "parent" violates not-null constraint
Detail: Failing row contains (null, Test 2).
我正在尝试的代码 运行:
val parent = Parent(
id = 0,
name = "Test 2"
).apply {
children = mutableSetOf(
Child(
id = 0,
name = "Test 21",
parent = this
).apply {
grandchildren =
mutableSetOf(
Grandchild(
id = 0,
name = "Test 211",
child = this
)
)
},
Child(
id = 0,
name = "Test 22",
parent = this
)
)
}
val saveParent: Parent = parentRepository.save(parent)
型号:
@Entity
class Parent(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
@OneToMany(mappedBy = "parent", cascade = [CascadeType.ALL])
var children: MutableSet<Child> = mutableSetOf()
)
@Entity
class Child(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
@ManyToOne @JoinColumn(name = "child_id")
var parent: Parent,
@OneToMany(mappedBy = "child", cascade = [CascadeType.ALL])
var grandchildren: MutableSet<Grandchild> = mutableSetOf()
)
@Entity
class Grandchild(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
@ManyToOne @JoinColumn(name = "child_id")
var child: Child
)
application.yml
spring:
datasource:
platform: postgres
url: jdbc:postgresql://localhost:5432/onetomany?ssl=false
driver-class-name: org.postgresql.Driver
initialization-mode: always
jpa:
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQLDialect
generate-ddl: false
hibernate:
ddl-auto: none
liquibase:
enabled: true
change-log: classpath:db/master.xml
liquibase 脚本:
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="bruce (generated)" id="data-1">
<createTable tableName="parent">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_PARENT"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-2">
<createTable tableName="child">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_CHILD"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="parent_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-3">
<createTable tableName="grandchild">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_GRANDCHILD"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="child_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-6">
<addForeignKeyConstraint baseColumnNames="parent_id" baseTableName="child" constraintName="FK_CHILD_PARENT"
deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="parent" validate="true"/>
</changeSet>
<changeSet author="bruce (generated)" id="data-8">
<addForeignKeyConstraint baseColumnNames="child_id" baseTableName="grandchild" constraintName="FK_CHILD_GRANDCHILD"
deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="child" validate="true"/>
</changeSet>
</databaseChangeLog>
@GeneratedValue(strategy = GenerationType.IDENTITY)
通常通过在数据库中指定一个自动递增的默认值来工作,例如 nextval('my_entity_sequence'::regclass)
。插入后,数据库将生成标识符。
在Postgres中,有serial
/bigserial
伪类型来指定自增列(会在内部创建序列以及列默认值),所以DDL可以例如看起来像这样:
create table my_entity ( id bigserial not null, primary key (id) )
https://www.postgresql.org/docs/current/datatype-numeric.html
在您的情况下,liquibase 错过了所有 ID 列的 type/defaults(现在只有“父”- 插入失败,但其他实体的插入也会失败)。
这是一个已知的 liquibase 问题:https://github.com/liquibase/liquibase/issues/1009 - 解决该问题的建议包括在变更集中手动指定 autoIncrement="true"
。
我正在尝试在 spring 中构建一个数据模型,该模型通过 one-to-many 关系向下级联最多 3 个级别,但我无法使其与 liquibase 脚本一起使用。
我正在使用 spring 引导与 Kotlin 和 liquibase 与 PostgreSQL 数据库。
到目前为止我做了什么:
- 减少代码以仅包含不起作用的部分(见下文)
- 我尝试了@OneToMany 和@JoinTable 以及@JoinColumn,我也尝试了@ManyToMany 来排除@OneToMany 的问题
- 我 运行 没有 liquibase 的相同代码(下面)让 Hibernate/JPA 从模型创建表
- 这确实有效,所以我从这些表中生成了 liquibase 脚本,但它们看起来和我自己的完全一样(键名除外)
- 使用这些模型检索数据有效(如果我直接通过 SQL 插入数据)
老实说我不确定,如果问题出在模型、配置或 liquibase 脚本中,所以我只 post 所有这些。我缺少配置吗?我是否正确配置了级联?我的模型 definitions/liquibase 脚本错了吗?
我在保存 parent 时遇到的异常是:
Hibernate: insert into parent (name) values (?)
2021-12-15 23:29:16.797 WARN 14115 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23502
2021-12-15 23:29:16.798 ERROR 14115 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: null value in column "id" of relation "parent" violates not-null constraint
Detail: Failing row contains (null, Test 1).
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [id" of relation "parent]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "id" of relation "parent" violates not-null constraint
Detail: Failing row contains (null, Test 2).
我正在尝试的代码 运行:
val parent = Parent(
id = 0,
name = "Test 2"
).apply {
children = mutableSetOf(
Child(
id = 0,
name = "Test 21",
parent = this
).apply {
grandchildren =
mutableSetOf(
Grandchild(
id = 0,
name = "Test 211",
child = this
)
)
},
Child(
id = 0,
name = "Test 22",
parent = this
)
)
}
val saveParent: Parent = parentRepository.save(parent)
型号:
@Entity
class Parent(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
@OneToMany(mappedBy = "parent", cascade = [CascadeType.ALL])
var children: MutableSet<Child> = mutableSetOf()
)
@Entity
class Child(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
@ManyToOne @JoinColumn(name = "child_id")
var parent: Parent,
@OneToMany(mappedBy = "child", cascade = [CascadeType.ALL])
var grandchildren: MutableSet<Grandchild> = mutableSetOf()
)
@Entity
class Grandchild(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
@ManyToOne @JoinColumn(name = "child_id")
var child: Child
)
application.yml
spring:
datasource:
platform: postgres
url: jdbc:postgresql://localhost:5432/onetomany?ssl=false
driver-class-name: org.postgresql.Driver
initialization-mode: always
jpa:
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQLDialect
generate-ddl: false
hibernate:
ddl-auto: none
liquibase:
enabled: true
change-log: classpath:db/master.xml
liquibase 脚本:
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="bruce (generated)" id="data-1">
<createTable tableName="parent">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_PARENT"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-2">
<createTable tableName="child">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_CHILD"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="parent_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-3">
<createTable tableName="grandchild">
<column name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="PK_GRANDCHILD"/>
</column>
<column name="name" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="child_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="bruce (generated)" id="data-6">
<addForeignKeyConstraint baseColumnNames="parent_id" baseTableName="child" constraintName="FK_CHILD_PARENT"
deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="parent" validate="true"/>
</changeSet>
<changeSet author="bruce (generated)" id="data-8">
<addForeignKeyConstraint baseColumnNames="child_id" baseTableName="grandchild" constraintName="FK_CHILD_GRANDCHILD"
deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="child" validate="true"/>
</changeSet>
</databaseChangeLog>
@GeneratedValue(strategy = GenerationType.IDENTITY)
通常通过在数据库中指定一个自动递增的默认值来工作,例如 nextval('my_entity_sequence'::regclass)
。插入后,数据库将生成标识符。
在Postgres中,有serial
/bigserial
伪类型来指定自增列(会在内部创建序列以及列默认值),所以DDL可以例如看起来像这样:
create table my_entity ( id bigserial not null, primary key (id) )
https://www.postgresql.org/docs/current/datatype-numeric.html
在您的情况下,liquibase 错过了所有 ID 列的 type/defaults(现在只有“父”- 插入失败,但其他实体的插入也会失败)。
这是一个已知的 liquibase 问题:https://github.com/liquibase/liquibase/issues/1009 - 解决该问题的建议包括在变更集中手动指定 autoIncrement="true"
。