user92161
user92161

Reputation: 117

Hibernate not deleting the foreign key row (rather setting it as NULL)

I have a RECIPE table that has OneToMany relationship with the INGREDIENT table because a single recipe can have many ingredients. The issue is that if a user deletes an ingredient (which sets all fields (ingredient_id and ingredient) to NULL by frontend), then the row containing relationship of both the tables RECIPE_INGREDIENT is deleted but the row in the Ingredient table still exists. Can't we tell Hibernate to delete that rows also?

Oracle table

create table recipe(id number primary key,
                    name varchar2(25) unique);

create table ingredient(ingredient_id number(4) primary key,
                    ingredient varchar2(40));

create table recipe_ingredient(recipe_id number(4),
                           ingredient_id number(4),
                           constraint recipe_fk foreign key(recipe_id)
                           references recipe(recipe_id),
                           constraint ingredient_fk foreign
                           key(ingredient_id) references
                           ingredient(ingredient_id));

Ingredient and Recipe POJO

@Entity
@Table(name = "ingredient", uniqueConstraints={
        @UniqueConstraint(columnNames="INGREDIENT_ID")
})
public class Ingredient implements Serializable {   
    @Id
    @Column(name = "INGREDIENT_ID", unique=true, nullable=false)
    @SequenceGenerator(name="seq_ingredient", sequenceName="seq_ingredient")
    @GeneratedValue(strategy=GenerationType.AUTO, generator="seq_ingredient")
    private Integer ingredientId;

    @Column(name = "INGREDIENT")
    private String ingredient;

    /*@ManyToOne(fetch=FetchType.EAGER)
    @JoinColumn(name="RECIPE_ID")
    private Recipe recipe;*/

    //getter and setters



@Entity
@Table(name = "recipe")
public class Recipe implements Serializable {    
    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name = "name")
    private String name;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    @JoinTable(name = "recipe_ingredient", joinColumns = { @JoinColumn(name = "recipe_id") }, inverseJoinColumns = { @JoinColumn(name = "ingredient_id") })
    private List<Ingredient> ingredients;

//getters and setter
}

DAO Code

public class RecipeDaoImpl implements RecipeDao {
    public void addRecipe(Recipe recipe) {
        getSession().saveOrUpdate(recipe);
    }
}

Log that shows that the row in INGREDIENT table still exists whereas Hibernate is just deleting row from 'RECIPE_INGREDIENT' table.

Please see following that ingredient_id with null is deleted. In both cases, it is updating ingredient.recipe_id as NULL.

Received following from frontend:
RecipeController - Recipe[recipeId=126,name=Sandwich,ingredients=[Ingredient[ingredientId=270,ingredient=Salt],[ingredientId=<null>,quantity=<null>]]]

Hibernate: update RECIPE set NAME=? where RECIPE_ID=?
Hibernate: update ingredient set INGREDIENT=? where INGREDIENT_ID=?
Hibernate: delete from recipe_ingredient where recipe_id=?
Hibernate: insert into recipe_ingredient (recipe_id, ingredient_id) values (?, ?)

So the database table has,

INDREDIENT
INGREDIENT_ID   INGREDIENT
271             Salt    
272             Sugar

RECIPE_INDGREDIENT
RECIPE_ID   INDREDIENT_ID
126         271

Upvotes: 3

Views: 4297

Answers (2)

LostMage
LostMage

Reputation: 491

I solved this issue by adding insertable = false, updatable = false as attributes to @JoinColumn

Like this:

@JoinColumn(name="RECIPE_ID", insertable = false, updatable = false)

Upvotes: 4

Ankur Singhal
Ankur Singhal

Reputation: 26067

Orphan Removal in Relationships

When a target entity in one-to-one or one-to-many relationship is removed from the relationship, it is often desirable to cascade the remove operation to the target entity. Such target entities are considered “orphans,” and the orphanRemoval attribute can be used to specify that orphaned entities should be removed.

For example, if an RECIPE has many INGREDIENT and one of them is removed from the list, the removed INGREDIENT is considered an orphan. If orphanRemoval is set to true, the line item entity will be deleted when the INGREDIENT is removed from the list.

The orphanRemoval attribute in @OneToMany and @oneToOne takes a Boolean value and is by default false.

The following example will cascade the remove operation to the orphaned INGREDIENT entity when it is removed from the relationship:

@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, orphanRemoval="true")
@JoinColumn(name="RECIPE_ID")
private List<Ingredient> ingredients;

To Add more

orphanRemoval and DELETE CASCADE.

orphanRemoval is an entirely ORM-specific thing. It marks "child" entity to be removed when it's no longer referenced from the "parent" entity, e.g. when you remove the child entity from the corresponding collection of the parent entity.

ON DELETE CASCADE is a database-specific thing, it deletes the "child" row in the database when the "parent" row is deleted.

Upvotes: 1

Related Questions