Reputation: 1061
I currently have a setup similar to:
@MappedSuperclass
public abstract class AbstractEntity implements Serializable {
private Long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
@Entity
public class Container extends AbstractEntity {
private Collection<Item> items = new HashSet<>();
@Cascade(CascadeType.SAVE_UPDATE)
@OneToMany(mappedBy = "container", orphanRemoval = true)
public Collection<Item> getItems() { return items; }
public void setItems(Collection<Item> items) { this.items = items; }
}
@Entity
public class Item extends AbstractEntity {
private Container container;
@ManyToOne(optional = false)
public Container getContainer() { return container; }
public void setContainer(Container container) { this.container = container; }
}
@Entity
public class FirstItemDetails extends AbstractEntity {
private Item item;
@OneToOne(optional = false)
@Cascade({CascadeType.DELETE, CascadeType.REMOVE})
public Item getItem() { return item; }
public void setItem(Item item) { this.item = item; }
}
@Entity
public class SecondItemDetails extends AbstractEntity {
private Item item;
@OneToOne(optional = false)
@Cascade({CascadeType.DELETE, CascadeType.REMOVE})
public Item getItem() { return item; }
public void setItem(Item item) { this.item = item; }
}
I've left out some of the unnecessary fields as they make no difference in the end. Now for the problem. What I want to do is something like:
public void removeItemFromContainer(Item item, Container container) {
Transaction transaction = session.beginTransaction();
container.getItems().remove(item);
session.save(container);
transaction.commit();
}
What I expect from this function is to physically remove the item from a given container and since I have orphanRemoval set to true, when I persist my container it does actually try to remove the item from database. The problem here lies with the ItemDetails entity which has a foreign_key constraint so when the commit is being ran I get:
org.hibernate.exception.ConstraintViolationException: could not execute statement
Caused by: java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: foreign key no action; FK_FITEM_DETAILS_ITEM table: first_item_details
Caused by: org.hsqldb.HsqlException: integrity constraint violation: foreign key no action; FK_FITEM_DETAILS_ITEM table: first_item_details
The most confusing part to me is that when I physically add ON DELETE CASCADE in first_item_details table in database rather than relying on hibernate to cascade for me, everything works. But this approach is error prone because if at some point I'll decide to use an interceptor or eventListener for my any of my details entity it simply won't work so I'd much prefer to allow hibernate to handle it rather than needing to rely on manual db structure change.
Upvotes: 0
Views: 1165
Reputation: 23246
You will need to make the relationship from Item to ItemDetails bidirectional. If Item doesn't know anything about this relationship why would the corresponding ItemDetail be deleted?
@Entity
public class Item extends AbstractEntity {
private Container container;
@OneToOne(mappedBy="item")
@Cascade({CascadeType.DELETE, CascadeType.REMOVE})
public ItemDetails itemDetails;
}
See a similar acceted answer here:
https://stackoverflow.com/a/7200221/1356423
While you note some issues with this, you should still be able to achieve this by using JPA Inheritance so that Item can still have a single relationship with an ItemDetails. The Inheritance Strategy will depend on your database structure:
http://en.wikibooks.org/wiki/Java_Persistence/Inheritance
Your updated code will then look like:
@Entity
@Inheritance(/*defineStrategy*/)
//define discriminator column if required
public abstract class ItemDetails extends AbstractEntity {
private Item item;
@OneToOne(optional = false)
@Cascade({CascadeType.DELETE, CascadeType.REMOVE})
public Item getItem() { return item; }
public void setItem(Item item) { this.item = item; }
}
@Entity
//define table or discriminator depending on strategy
public class FirstItemDetails extends ItemDetails {
//map fields specific to thus sub-class
}
@Entity
//define table or discriminator depending on strategy
public class SecondItemDetails extends ItemDetails {
//map fields specific to thus sub-class
}
This will also have the benefit of your not having to repeat the mapping to Item in each ItemDetails class, although it will come at the expense of an extra join if your are using a table per class.
Upvotes: 1