I have looked for information on how to implement the following association in hibernate and although the hibernate manual is very thorough, I haven't found the following use case addressed.
I have a requirement to have an association between to entities, where the association has several attributes besides the foreign keys to the associated entities. The specifications for the relation are:
- A Container is associated to Contained through Position.
- A Position can not exist without both a Container and a Contained item.
- Hence, if either the Container or the Contained item is deleted, the Position should be deleted.
- A Container can contain 0 or more Positions.
- A Position refers to one and only one Contained item.
I have managed to configure most of the requirements through annotations and it works out quite nicely, except for cascading the delete from the Contained item. I have a work around to do this described below, but I would like to configure this action through annotations to have the database automatically do the work in order to have a more robust referential integrity.
This is the mapping that I have so far:
@Entity
public class Container
{
@OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.EAGER)
@JoinColumn(name = "container_fk")
public Set<Position> getPositions() { return this.positions; }
public void setPositions(final Set<Position> positions) { this.positions = positions; }
private Set<Position> positions;
...
}
@Entity
public class Position
{
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "container_fk", insertable = false, updatable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
public Container getContainer() { return this.container; }
public void setContainer(Container container) { this.container = container; }
private Container container;
@NaturalId
@OneToOne(optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "contained_fk")
public Contained getContained() { return this.contained; }
public void setContained(Contained contained) { this.contained = contained; }
private Contained contained;
// other attributes are owned by this relationship
// (i.e., they don't make sense in either Container or Contained.
...
}
To delete the Position, when deleting the Contained item the following is implemented in code in a ContainedDao (presented without exception handling and session management is done in the base Dao class for simplicity):
@Repository
@Transactional(rollbackFor = Throwable.class)
public class ContainedDao extends TransactionalDao<Contained>
{
public void delete(String id)
{
final Session session = getSession();
// If there is a Position associated to the Contained item delete it,
// and remove it from any Container collection.
Position position = (Position) session.createCriteria(Position.class)
.createCriteria("contained")
.add(Restrictions.eq("id", id))
.uniqueResult();
if (position != null)
{
position.getContainer().getPositions().remove(position);
session.delete(position);
}
// Delete the Contained item.
Contained object = session.load(Contained.class, id);
session.delete(contained);
}
}
What I would like to do is to somehow configure through annotations so that the ContainedDao.delete method is simplified to a simple:
// Delete the Contained item.
Contained object = session.load(Contained.class, id);
session.delete(contained);
Is this possible? Or is my current solution the best I can get? Is there a better way to approach this? Note that a key factor here is that Position containes additional attributes; otherwise, I would have configured the association between Container/Contained directly and let hibernate manage the association.