org.hibernate.exception.ConstraintViolationException 当使用多个 ManyToMany 关系时
org.hibernate.exception.ConstraintViolationException when using multiple ManyToMany relations
当运行以下代码时,我得到一个org.hibernate.exception.ConstraintViolationException
异常
抱怨
org.h2.jdbc.JdbcSQLException: NULL not allowed for column "SECONDARYELEMENTS_ID";
我知道这是由于从 Container 对象到 Element 对象有两个 @ManyToMany
关系造成的。如果我删除
@ManyToMany(cascade = CascadeType.ALL)
List<Element> secondaryElements;
从容器 class 一切正常。
我在这里错过了什么?
如果您需要更多信息,请告诉我。
@Transactional
public class JPA2Runner {
//hidding Spring Data JPA repository
@Autowired
ContainerDAO containerDAO;
public boolean run() throws Exception{
Container container1 = new Container( );
Container container2 = new Container( );
Element element1 = new Element( container1, container2);
Element element2 = new Element( container2, container1);
container1.getPrimaryElements().add(element1);
container1.getSecondaryElements().add(element2);
container2.getPrimaryElements().add(element2);
container2.getSecondaryElements().add(element1);
containerDAO.saveContainer(container1);
return true;
}
}
@Entity
public class Container extends AbstractEntity {
@ManyToMany(cascade = CascadeType.ALL)
List<Element> primaryElements;
@ManyToMany(cascade = CascadeType.ALL)
List<Element> secondaryElements;
public Container( ){
primaryElements =new ArrayList<Element>();
secondaryElements = new ArrayList<Element>();
}
}
@Entity
public class Element extends AbstractEntity {
@ManyToOne(cascade = CascadeType.ALL)
private Container dedicatedContainer1;
@ManyToOne(cascade = CascadeType.ALL)
private Container dedicatedContainer2;
public Element(){}
public Element(Container container1, Container container2){
this.dedicatedContainer1 = container1;
this.dedicatedContainer2 = container2;
}
}
更新 1:
难道是为了防止同一类型有多个关系,需要指定@JoinTable?
更新二:
感谢@ducksteps 的提示和评论,我能够找到解决该问题的方法。
问题是上面的定义为两个元素列表生成了一个带有键的连接 table,即
create table Container_Element (Container_id bigint not null, secondaryElements_id bigint not null, primaryElements_id bigint not null)
但是,保存容器会在连接中生成以下插入内容 table
insert into Container_Element (Container_id, primaryElements_id) values (?, ?)
这会导致 ConstraintViolation 异常。一个修复似乎是使用
明确定义两个 Join tables
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="Container_PrimaryElements")
List<Element> primaryElements;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="Container_SecondaryElements")
List<Element> secondaryElements;
这似乎有效。
不过,我还在想
- 这个问题有更好的解决方案吗?
- 对 ManyToMany 关系使用联接 table "should" 真的有效(根据规范)?
我能想到两个可能的原因:
CONTAINER_ELEMENT
关系 table 中 SECONDARYELEMENTS_ID
列的 NOT NULL
或 REFERENCES
约束不可延迟。在 saveContainer()
调用期间,您持久化了与非持久化实体的关系。由于此关系是循环的(Element
与 Container
相关,Element
与 [...] 相关),这无法通过重新排序来解决。我不确定 h2 如何处理这个问题,但我在使用 Postgres 时 运行 遇到了这个问题。
ID 生成(在您的情况下)不适用于级联规则。出于某种原因,Element
没有获得生成的 ID - 因此 JPA 使用 ID NULL
保留它(如果您的 ELEMENT
table 允许)。
Let me know if you need more information.
来自你:
调试输出,尤其是生成的 SQL 语句(最好带有响应)将有助于找出您的交易失败的时间点。您的 table 定义(CREATE TABLE [...]
,尤其是约束定义)将有助于确定第一个原因在这种情况下是否可能是个问题。
来自他人:
对 h2 更有经验的人可以判断您是否需要一些 "magic"(例如使 REFERENCES
在 Postgres 中可延迟)来插入具有循环关系的数据。
Update 1: Could it be that it is required to specify the @JoinTable in case there are multiple relations to the same type?
有可能。规范有这个例子:
Entity Employee is mapped to a table named EMPLOYEE.
Entity Patent is mapped to a table named PATENT.
There is a join table that is named EMPLOYEE_PATENT (owner name first). This join table
has two foreign key columns. One foreign key column refers to table EMPLOYEE and has the
same type as the primary key of EMPLOYEE. This foreign key column is named
EMPLOYEE_, where denotes the name of the
primary key column of table EMPLOYEE.The other foreign key column refers to table
PATENT and has the same type as the primary key of PATENT. This foreign key column is
named PATENTS_, where denotes the name of the primary key column of table PATENT.
所以table名称是从实体名称派生的,它的列名称是从关系目标列名称和字段名称派生的。你的连接 table 有什么结构?如果它有两列以上,那就是你的问题。
Update 2: [...] better solutions for this issue?
取决于您的用例。您可以使用另一个继承级别,即 PrimaryElement extends Element
和 SecondaryElement extends Element
,然后使用单个字段 List<Element> elements
来存储您的数据(您仍然可以查询特定类型)。当然,这仅在 Element
的类型是主要 xor 次要时才有效。
Otoh,使用两个连接 tables 甚至可能会更好,同样取决于您的用例(额外的 JOIN 与更大的 table)。
using a join table for both ManyToMany relations "should" actually work (according to the specification)
尝试删除 primaryElements_id
和 secondaryElements_id
的 NOT NULL 约束。
当运行以下代码时,我得到一个org.hibernate.exception.ConstraintViolationException
异常
抱怨
org.h2.jdbc.JdbcSQLException: NULL not allowed for column "SECONDARYELEMENTS_ID";
我知道这是由于从 Container 对象到 Element 对象有两个 @ManyToMany
关系造成的。如果我删除
@ManyToMany(cascade = CascadeType.ALL)
List<Element> secondaryElements;
从容器 class 一切正常。
我在这里错过了什么?
如果您需要更多信息,请告诉我。
@Transactional
public class JPA2Runner {
//hidding Spring Data JPA repository
@Autowired
ContainerDAO containerDAO;
public boolean run() throws Exception{
Container container1 = new Container( );
Container container2 = new Container( );
Element element1 = new Element( container1, container2);
Element element2 = new Element( container2, container1);
container1.getPrimaryElements().add(element1);
container1.getSecondaryElements().add(element2);
container2.getPrimaryElements().add(element2);
container2.getSecondaryElements().add(element1);
containerDAO.saveContainer(container1);
return true;
}
}
@Entity
public class Container extends AbstractEntity {
@ManyToMany(cascade = CascadeType.ALL)
List<Element> primaryElements;
@ManyToMany(cascade = CascadeType.ALL)
List<Element> secondaryElements;
public Container( ){
primaryElements =new ArrayList<Element>();
secondaryElements = new ArrayList<Element>();
}
}
@Entity
public class Element extends AbstractEntity {
@ManyToOne(cascade = CascadeType.ALL)
private Container dedicatedContainer1;
@ManyToOne(cascade = CascadeType.ALL)
private Container dedicatedContainer2;
public Element(){}
public Element(Container container1, Container container2){
this.dedicatedContainer1 = container1;
this.dedicatedContainer2 = container2;
}
}
更新 1: 难道是为了防止同一类型有多个关系,需要指定@JoinTable?
更新二: 感谢@ducksteps 的提示和评论,我能够找到解决该问题的方法。 问题是上面的定义为两个元素列表生成了一个带有键的连接 table,即
create table Container_Element (Container_id bigint not null, secondaryElements_id bigint not null, primaryElements_id bigint not null)
但是,保存容器会在连接中生成以下插入内容 table
insert into Container_Element (Container_id, primaryElements_id) values (?, ?)
这会导致 ConstraintViolation 异常。一个修复似乎是使用
明确定义两个 Join tables@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="Container_PrimaryElements")
List<Element> primaryElements;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="Container_SecondaryElements")
List<Element> secondaryElements;
这似乎有效。
不过,我还在想
- 这个问题有更好的解决方案吗?
- 对 ManyToMany 关系使用联接 table "should" 真的有效(根据规范)?
我能想到两个可能的原因:
CONTAINER_ELEMENT
关系 table 中SECONDARYELEMENTS_ID
列的NOT NULL
或REFERENCES
约束不可延迟。在saveContainer()
调用期间,您持久化了与非持久化实体的关系。由于此关系是循环的(Element
与Container
相关,Element
与 [...] 相关),这无法通过重新排序来解决。我不确定 h2 如何处理这个问题,但我在使用 Postgres 时 运行 遇到了这个问题。ID 生成(在您的情况下)不适用于级联规则。出于某种原因,
Element
没有获得生成的 ID - 因此 JPA 使用 IDNULL
保留它(如果您的ELEMENT
table 允许)。
Let me know if you need more information.
来自你:
调试输出,尤其是生成的 SQL 语句(最好带有响应)将有助于找出您的交易失败的时间点。您的 table 定义(CREATE TABLE [...]
,尤其是约束定义)将有助于确定第一个原因在这种情况下是否可能是个问题。
来自他人:
对 h2 更有经验的人可以判断您是否需要一些 "magic"(例如使 REFERENCES
在 Postgres 中可延迟)来插入具有循环关系的数据。
Update 1: Could it be that it is required to specify the @JoinTable in case there are multiple relations to the same type?
有可能。规范有这个例子:
Entity Employee is mapped to a table named EMPLOYEE. Entity Patent is mapped to a table named PATENT. There is a join table that is named EMPLOYEE_PATENT (owner name first). This join table has two foreign key columns. One foreign key column refers to table EMPLOYEE and has the same type as the primary key of EMPLOYEE. This foreign key column is named EMPLOYEE_, where denotes the name of the primary key column of table EMPLOYEE.The other foreign key column refers to table PATENT and has the same type as the primary key of PATENT. This foreign key column is named PATENTS_, where denotes the name of the primary key column of table PATENT.
所以table名称是从实体名称派生的,它的列名称是从关系目标列名称和字段名称派生的。你的连接 table 有什么结构?如果它有两列以上,那就是你的问题。
Update 2: [...] better solutions for this issue?
取决于您的用例。您可以使用另一个继承级别,即 PrimaryElement extends Element
和 SecondaryElement extends Element
,然后使用单个字段 List<Element> elements
来存储您的数据(您仍然可以查询特定类型)。当然,这仅在 Element
的类型是主要 xor 次要时才有效。
Otoh,使用两个连接 tables 甚至可能会更好,同样取决于您的用例(额外的 JOIN 与更大的 table)。
using a join table for both ManyToMany relations "should" actually work (according to the specification)
尝试删除 primaryElements_id
和 secondaryElements_id
的 NOT NULL 约束。