使用多个ManyToMany关系时org.hibernate.exception.ConstraintViolationException

时间:2015-04-08 23:12:38

标签: java spring hibernate jpa many-to-many

运行以下代码时,我收到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;
来自Container类的

一切运行正常。

我在这里缺少什么?

如果您需要更多信息,请与我们联系。

@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,以防有多个关系到同一类型?

更新2: 感谢@ducksteps的提示和评论,我找到了解决问题的方法。 问题是上面的定义生成了一个连接表,其中包含两个元素列表的键,即

create table Container_Element (Container_id bigint not null, secondaryElements_id bigint not null, primaryElements_id bigint not null)

但是,保存容器会在连接表中生成以下插入

 insert into Container_Element (Container_id, primaryElements_id) values (?, ?)

导致ConstraintViolation异常。修复似乎是使用

显式定义两个Join表
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="Container_PrimaryElements")
List<Element> primaryElements;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name="Container_SecondaryElements")
List<Element> secondaryElements;

似乎有用。

然而,我仍然想知道是否

  • 这个问题有更好的解决方案吗?
  • 为ManyToMany关系使用连接表&#34;应该&#34;实际工作(根据规范)?

1 个答案:

答案 0 :(得分:1)

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

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

  2. ID生成(在您的情况下)不适用于级联规则。出于某种原因,Element没有获得生成的ID - 因此JPA会将其标识为NULL(如果您的ELEMENT表允许)。

  3.   

    如果您需要更多信息,请与我们联系。

    来自你:

    调试输出,尤其是生成的SQL语句(理想情况下是响应)有助于找出事务失败的时间点。在这种情况下,您的表定义(CREATE TABLE [...],尤其是约束定义)将有助于确定第一个原因是否可能是一个问题。

    来自其他人:

    对h2更有经验的人可以判断你是否需要一些&#34;魔法&#34; (比如在Postgres中使REFERENCES可推迟)以插入具有循环关系的数据。

      

    更新1 :是否需要指定@JoinTable,以防多个关系出现在同一类型的情况下?

    可能的。规范有这个例子:

      

    Entity Employee映射到名为EMPLOYEE的表。   实体专利被映射到名为PATENT的表。   有一个名为EMPLOYEE_PATENT(所有者名称优先)的连接表。这个连接表   有两个外键列。一个外键列是指表EMPLOYEE并且具有   与EMPLOYEE的主键相同的类型。此外键列已命名   EMPLOYEE_,其中表示的名称   表EMPLOYEE的主键列。其他外键列是指表   PATENT并且与PATENT的主键具有相同的类型。这个外键列是   名为PATENTS_,其中表示表PATENT的主键列的名称。

    因此,表名是从实体名称派生而来的,它的列名是从关系目标列名和字段名中派生出来的。您的联接表有什么结构?如果它有两列以上,那就是你的问题。

      

    更新2 :[...]此问题的更好解决方案?

    取决于您的使用案例。您可以使用其他级别的继承,即PrimaryElement extends ElementSecondaryElement extends Element,然后使用单个字段List<Element> elements来存储您的数据(您仍然可以查询特定类型)。当然,这只适用于Element的类型是主要xor或辅助。

    Otoh,使用两个连接表可能会更好,再次取决于您的用例(额外的JOIN与更大的表)。

      

    为ManyToMany关系使用连接表&#34;应该&#34;实际工作(根据规范)

    尝试删除primaryElements_idsecondaryElements_id的NOT NULL约束。