How to properly implement a owner-owned-owner2 association in java with hibernate?
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 th开发者_JAVA技巧e 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.
When you say "A Position refers to one and only one Contained item", do you mean a one-to-one relationship (as opposed to a many-to-one)? If that is the case, then I suppose what you really want to express here is that Position is-a Contained entity.
The single difference between a one-to-one and an is-a relationship is in the life-cycle of the relationship. In a one-to-one relationship the Contained instance to which the Position points to might change. An is-a relationship is more restricted: the relationship is essentially the identity of the Position itself, therefore changing it is not allowed.
To implement an is-a relationship you need three things:
- The foreign-key from Position to Contained should also be the primary-key of Position. Since primary keys don't change (I am allowing myself the luxurious presumption here that you are using an auto-generated surrogate key on Contained) the is-a relationship cannot be updated either. When you delete a row from Contained, you have to remove any referencing Positions too (there can be at most one).
- The Position class should extend the Contained class. As above with the database model, an extends declaration will stay as such for the whole life-cycle of a Position object. When you delete the Contained object, your Position is gone too (they are the same object in memory, only accessed through different types of references)
- In your Hibernate configuration specify Position to be a joined-subclass of Contained.
Then following DAO code to delete a Contained instance would also cascade to Position:
public void deleteContained(String id)
{
Contained c = (Contained) session.createCriteria(Contained.class)
.add(Restrictions.eq("id", id))
.uniqueResult();
// Removes associated Position too, if exists
session.delete(c);
// This would fail now:
// session.load(Position.class, id);
}
Also, deleting a Position instance would cascade to Contained:
public void deletePosition(String id)
{
Position p = (Position) session.createCriteria(Position.class)
.add(Restrictions.eq("id", id))
.uniqueResult();
// Removes associated Contained too
session.delete(p);
// This would fail now:
// session.load(Contained.class, id);
}
P.S. A one-to-one relationship is a bogus concept anyway, an OOP illusion. Instead of using a one-to-one, such relationships should either be properly restricted to an is-a, or reconsidered as a many-to-one. You can substitute most of my points about one-to-one with many-to-one above.
精彩评论