Update: I researched it a little bit and found that people have made an Association Entity to solve this sort of problem. There are some limitations with doing that, but the end result doesn't look overly terrible. Still, there might be some better ways to design this that I haven't thought about yet.
The Parent
entity will have a OneToMany
mapping to the ParentChild
association entity. The mappedBy
attribute must be set to force the usage of the association entity:
@Entity
public class Parent {
@Id @GeneratedValue private Long id;
@OneToMany(mappedBy="parent", cascade=CascadeType.ALL)
private List<ParentChild> children = new ArrayList<ParentChild>();
public void addChild(Child child, boolean teamLead) {
ParentChild association = new ParentChild();
association.setChild(child);
association.setParent(this);
association.setTeamLead(teamLead);
children.add(association);
child.setParent(this);
}
public Long getId() { return id; }
}
and the child entity will hold a reference to the Parent entity, as is usual with a unidirectional ManyToMany
relationship.
@Entity
public class Child {
@Id @GeneratedValue private Long id;
@ManyToOne
private Parent parent;
public Parent getParent() { return parent; }
public void setParent(Parent parent) { this.parent = parent; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
Then the association entity will have ManyToOne
references to both entities, plus a primary key. The association references use the same id field as the primary key
because of the @MapsId
annotation.
@Entity
public class ParentChild {
@EmbeddedId
private ParentChildKey id;
@ManyToOne
@MapsId("parentId")
private Parent parent;
@ManyToOne
@MapsId("childId")
private Child child;
private boolean teamLead;
public ParentChild() { id = new ParentChildKey(); }
public boolean isTeamLead() { return teamLead; }
public void setTeamLead(boolean teamLead) { this.teamLead = teamLead; }
public Parent getParent() { return parent; }
public void setParent(Parent parent) { this.parent = parent; id.setParentId(parent.getId());}
public Child getChild() { return child; }
public void setChild(Child child) { this.child = child; id.setChildId(child.getId());}
}
Nothing special about the primary key entity.
@Embeddable
public class ParentChildKey implements Serializable {
private static final long serialVersionUID = 1L;
private Long parentId;
private Long childId;
public Long getParentId() { return parentId; }
public void setParentId(Long parentId) { this.parentId = parentId; }
public Long getChildId() { return childId; }
public void setChildId(Long childId) { this.childId = childId; }
... getters and setters
}
So, it did an insert and select for me without too much trouble, but you can see it seems like a lot of extra work just for an extra field. Of course, the extra field (in this case, teamLead
) is stored efficiently, but I can imagine a sort of "roles" type configuration that would be easier to work with and make more sense in the long run.