不能删除一对多关系中的孩子

问题描述:

当我把inverse=true写入set时,什么都不会被删除。当我不这样做,我从set删除MealIngredient,则Hibernate尝试设置null,它失败并抛出异常:不能删除一对多关系中的孩子

[SQLITE_CONSTRAINT] Abort due to constraint violation (MealIngredients.mealId may not be NULL) 

下面是XML映射:

<class name="restaurant.meal.Meal" table="Meals"> 
    <id name="id" type="integer"> 
     <column name="id" not-null="true" unique="true"/> 
     <generator class="increment"/> 
    </id> 
    <!-- some other, simple properties --> 

    <set name="ingredientsSet" cascade="all" lazy="false"> 
     <key> 
      <column name="mealId" not-null="true" /> 
     </key> 
     <one-to-many class="restaurant.meal.MealIngredient" /> 
    </set> 
</class> 

<class name="restaurant.meal.MealIngredient" table="MealIngredients"> 
    <composite-id name="id" class="restaurant.meal.MealIngredient$Id"> 
     <key-property name="ingredientId" /> 
     <key-property name="mealId" /> 
    </composite-id> 
    <many-to-one name="ingredient" class="restaurant.storage.Ingredient" insert="false" update="false" lazy="false"> 
     <column name="ingredientId" not-null="true" /> 
    </many-to-one> 
    <many-to-one name="meal" class="restaurant.meal.Meal" insert="false" update="false" lazy="false"> 
     <column name="mealId" not-null="true" /> 
    </many-to-one> 
    <!-- other properties --> 
</class> 

是的,关系在MealIngredient之间是多对多连接表MealIngredient(是的,我必须映射MealIngredient,因为该表中有附加列)。

question对我没有帮助,this也没有帮助。

编辑: 只有插入与当前映射一起工作,更新只是在MealIngredient表中生成另一行。


编辑2:hashCodeequals实现:

MealIngredient的$ id:(使用Apache commons-langEqualsBuilderHashCodeBuilder

@Override 
public boolean equals(Object o) { 
    if(!(o instanceof Id)) 
     return false; 

    Id other = (Id) o; 
    return new EqualsBuilder() 
       .append(this.getMealId(), other.getMealId()) 
       .append(this.getIngredientId(), other.getIngredientId()) 
       .isEquals(); 
} 

@Override 
public int hashCode() { 
    return new HashCodeBuilder() 
       .append(this.getMealId()) 
       .append(this.getIngredientId()) 
       .hashCode(); 
} 

MealIngredient

@Override 
public boolean equals(Object o) 
{ 
    if(!(o instanceof MealIngredient)) 
     return false; 

    MealIngredient other = (MealIngredient) o; 
    return this.getId().equals(other.getId()); 
} 

@Override 
public int hashCode() 
{ 
    return this.getId().hashCode(); 
} 

我检查日志,虽然我不知道什么Hibernate的引擎盖下做的,但它确实让insertMealIngredient

15:42:53,122 TRACE IntegerType:172 - returning '5' as column: quantity3_ 
Hibernate: 
    insert 
    into 
     MealIngredients 
     (quantity, ingredientId, mealId) 
    values 
     (?, ?, ?) 
15:42:53,131 TRACE IntegerType:133 - binding '16' to parameter: 1 
15:42:53,131 TRACE IntegerType:133 - binding '5' to parameter: 2 
15:42:53,131 TRACE IntegerType:133 - binding '1' to parameter: 3 

;当我从删除MealIngredient Hibernate使update并尝试将mealId设置为null

Hibernate: 
    update 
     MealIngredients 
    set 
     quantity=? 
    where 
     ingredientId=? 
     and mealId=? 
15:48:57,529 TRACE IntegerType:126 - binding null to parameter: 1 
15:48:57,529 TRACE IntegerType:133 - binding '1' to parameter: 2 
15:48:57,531 TRACE IntegerType:133 - binding '1' to parameter: 3 
15:48:57,535 WARN JDBCExceptionReporter:77 - SQL Error: 0, SQLState: null 
15:48:57,535 ERROR JDBCExceptionReporter:78 - [SQLITE_CONSTRAINT] Abort due to constraint violation (MealIngredients.quantity may not be NULL) 

不幸的是,似乎Hibernate不能很好地处理复合主键。我不得不在多对多连接表中添加额外的ID列(如我的MealIngredient)并使用它。

当我使用额外的ID作为主键后,插入/更新/删除按预期工作(即使cascade设置为delete-orphan,级联删除工作!)。

我为实体MealMealIngredient提供最终映射,以备将来参考。我希望这会帮助其他人,当他们偶然发现与连接表中的其他属性/列之间的多对多关系时。

<class name="restaurant.meal.Meal" table="Meals"> 
    <id name="id" type="integer"> 
     <column name="id" not-null="true" unique="true"/> 
     <generator class="increment"/> 
    </id> 
    <!-- additional properties --> 

    <set name="ingredientsSet" table="MealIngredients" cascade="all-delete-orphan" lazy="false" inverse="true"> 
     <key update="true"> 
      <column name="mealId" not-null="true" /> 
     </key> 
     <one-to-many class="restaurant.meal.MealIngredient" /> 
    </set> 
</class> 
<class name="restaurant.meal.MealIngredient" table="MealIngredients"> 
    <id name="id" type="integer"> 
     <column name="id" not-null="true" unique="true"/> 
     <generator class="increment"/> 
    </id> 
    <many-to-one name="ingredient" column="ingredientId" not-null="true" class="restaurant.storage.Ingredient" lazy="false" /> 
    <many-to-one name="meal" column="mealId" not-null="true" class="restaurant.meal.Meal" lazy="false" /> 

    <!-- additional properties --> 
</class> 

我相信你要找的解释是here。好吧,有点。不要读他的解释,它会让我困惑。尽管如此,他的例子很棒。

所以,不管怎么说,我觉得你想要做下列操作之一:

inverse=false并从成分中删除mealIngredient 收集并保存了一顿

inverse=true,并具有为null餐实例在MealIngredient变量并保存MealIngredient

编辑:以插入,而不是更新的问题,可能是由于事实上你没有覆盖hashcode和equals。如果您使用的是Eclipse,我相信它可以为您做到这一点,但您必须告诉它在自动生成方法时使用组合键的两个属性。每Hibernate documentation chapter 5

的持久化类必须重载equals()和hashCode()来 实现组合的标识符平等。它还必须实现 可序列化。

+0

那么,第一个选项是我在找什么。我已经尝试了许多变种的映射(''inverse'用'true' /'false',改变'Meal'映射中的'cascade'选项以及'MealIngredient')。没有任何工作。实际上,现在它甚至不重复记录,它只是不做任何事情(甚至不更新Meal中的其他属性)。关于'equals()'和'hashCode()',我已经实现了它们,但它仍然不起作用。 – mnn 2013-04-27 13:09:49

+0

@mnn您的'MealIngredient.equals()'和'MealIngredient.hacode()'方法不应该使用Hibernate id进行比较。请记住,当你第一次创建它时,如果你使用了一个自动生成的键,那么它将会是'null',直到你坚持这个对象。更多信息[这里](https://community.jboss.org/wiki/EqualsAndHashCode)(我认为这不会解决您的问题,但稍后会有所帮助)。 – 2013-04-27 14:48:15

+0

@mnn从跟踪中看来,Hibernate看起来像是试图将你的mealIdredient的数量设置为null,并且该列有'not null'。另外,在睡觉之后,从“Meal”中删除“MealIngredient”应该可能导致“MealIngredient”被“删除()”,因为我敢打赌“mealId”也是“非空”。 – 2013-04-27 15:03:10