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;

这似乎有效。

不过,我还在想

我能想到两个可能的原因:

  1. CONTAINER_ELEMENT 关系 table 中 SECONDARYELEMENTS_ID 列的 NOT NULLREFERENCES 约束不可延迟。在 saveContainer() 调用期间,您持久化了与非持久化实体的关系。由于此关系是循环的(ElementContainer 相关,Element 与 [...] 相关),这无法通过重新排序来解决。我不确定 h2 如何处理这个问题,但我在使用 Postgres 时 运行 遇到了这个问题。

  2. 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 ElementSecondaryElement 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_idsecondaryElements_id 的 NOT NULL 约束。