不能删除一对多关系中的孩子
当我把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>
是的,关系在Meal
和Ingredient
之间是多对多连接表MealIngredient
(是的,我必须映射MealIngredient
,因为该表中有附加列)。
编辑: 只有插入与当前映射一起工作,更新只是在MealIngredient
表中生成另一行。
编辑2:hashCode
和equals
实现:
MealIngredient的$ id:(使用Apache commons-lang
EqualsBuilder和HashCodeBuilder)
@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的引擎盖下做的,但它确实让insert
到MealIngredient
:
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
,级联删除工作!)。
我为实体Meal
和MealIngredient
提供最终映射,以备将来参考。我希望这会帮助其他人,当他们偶然发现与连接表中的其他属性/列之间的多对多关系时。
<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()来 实现组合的标识符平等。它还必须实现 可序列化。
那么,第一个选项是我在找什么。我已经尝试了许多变种的映射(''inverse'用'true' /'false',改变'Meal'映射中的'cascade'选项以及'MealIngredient')。没有任何工作。实际上,现在它甚至不重复记录,它只是不做任何事情(甚至不更新Meal中的其他属性)。关于'equals()'和'hashCode()',我已经实现了它们,但它仍然不起作用。 – mnn 2013-04-27 13:09:49
@mnn您的'MealIngredient.equals()'和'MealIngredient.hacode()'方法不应该使用Hibernate id进行比较。请记住,当你第一次创建它时,如果你使用了一个自动生成的键,那么它将会是'null',直到你坚持这个对象。更多信息[这里](https://community.jboss.org/wiki/EqualsAndHashCode)(我认为这不会解决您的问题,但稍后会有所帮助)。 – 2013-04-27 14:48:15
@mnn从跟踪中看来,Hibernate看起来像是试图将你的mealIdredient的数量设置为null,并且该列有'not null'。另外,在睡觉之后,从“Meal”中删除“MealIngredient”应该可能导致“MealIngredient”被“删除()”,因为我敢打赌“mealId”也是“非空”。 – 2013-04-27 15:03:10