Question

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)
Was it helpful?

Solution 2

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>

OTHER TIPS

I believe the explanation you're looking for is here. Well, sort of. Don't read his explanation, it confuses me. His examples are excellent though.

So, anyways, I think you want to do one of the following:

inverse=false and remove the mealIngredient from your ingredients collection and then save the Meal

inverse=true and have to null the meal instance variable in MealIngredient and save the MealIngredient

EDIT: The issue with inserts instead of updates is probably due to the fact that you have not over-ridden hashcode and equals. If you're using Eclipse, I believe it can do it for you, but you must tell it to use both properties of your composite key when it auto generates the methods. Per Hibernate documentation chapter 5:

The persistent class must override equals() and hashCode() to implement composite identifier equality. It must also implement Serializable.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top