When I put inverse=true
into set
, nothing gets deleted. When I don't, and I remove MealIngredient
from set
, then Hibernate tries to set null
, it fails and exception is thrown:
[SQLITE_CONSTRAINT] Abort due to constraint violation (MealIngredients.mealId may not be NULL)
Here are XML mappings:
<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>
Yes, the relationship between Meal
and Ingredient
is many-to-many with join table MealIngredient
(and yes, I have to map MealIngredient
as well, because of additional columns in that table).
This question did not help me, neither did this.
Edit:
Only inserting works with current mapping, update just generates another row in MealIngredient
table.
Edit 2: hashCode
and equals
implementations:
MealIngredient$Id: (uses Apache commons-lang
EqualsBuilder and 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();
}
I checked log and although I don't know what Hibernate does under the hood, but it does make the insert
into 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
And when I remove MealIngredient
from Meal.ingredientsSet
, Hibernate makes update
and tries to set mealId
to 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)
Unfortunately, it seems that Hibernate does not work well with composite primary keys. I had to add extra ID column into many-to-many join tables (like my MealIngredient
) and work with that.
After I use extra ID as primary key, inserting/updating/deleting works as expected (even with cascade
set to delete-orphan
, cascade deleting works!).
I provide final mappings for entities Meal
and MealIngredient
, for future reference. I hope this will help others, when they stumble upon many-to-many relationships with additional properties/columns in join table.
<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>