I’m having a hard time creating the optimal JPA representation of a relationship. Seems like there is a bunch of ways to do it and I’m not sure which is most optimal. Without JPA I might represent the structure as a PERSON table and a FRIEND table. Let’s say the PERSON table just has an ID and NAME. The FRIEND table has an OWNER_ID, a PERSON_ID, and a settable boolean IS_ACTIVE field. OWNER_ID and PERSON_ID both refer to the PERSON table. A person can have many friends so the unique primary key would be on OWNER_ID and PERSON_ID. In my code I would like the PersonEntity to control the relationship. Operations in Java might look like this:
person1.getFriends().add( new Friend( person2, isActive ) );
Friend friend = person1.findFriend( person2ID );
Something like that. Note that I would like JPA to implicitly assign the OWNER of the friend relationship. In the above code I do not pass it in the Friend constructor, I only pass the PERSON part of the composite key.
Here was my first guess at this:
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
@OneToMany( mappedBy="key.owner", cascade=CascadeType.ALL, orphanRemoval = true)
private Set<Friend> friends = new HashSet<>();
// getter and setter for Friends
...
}
@Entity
public class Friend {
@EmbeddedId
private Key key = new Key();
private boolean isActive;
...
@Embeddable
public static class Key implements Serializable {
@ManyToOne
private Person owner;
@OneToOne
private Person person;
...
}
}
But when I use this code I get stack overflow errors. I’m assuming it is confused by the OneToOne relationship.
What is the best way to model this sort of relationship in JPA 2.0? I suppose the simplest thing would be to introduce a generated long key in Friend. But that seems like it will make the query more complex for “find friends for john” since I will be introducing a third mapping table, basically mapping the relationship twice in SQL. Another approach is to make it a unidirectional OneToMany relationship and not even specify OWNER explicitly in Friend. That would introduce another table for the mapping and make queries a little convoluted. Using ElementCollection instead of OneToMany seems straight forward but then my understanding is that I could not manage Friend as an entity?
Let me know if you guys need anymore detail or requirements. BTW, I tried putting a MapsId in Friend for the Owner part of the PKEY but that gave me runtime errors.
Any help is appreciated.
NOTE: Even if I add a generated key to Friend I would like to enforce the uniqueness on the combination of {owner,person} in the Friend entity.
Alright, so if you want a solution to this problem that creates the sort of table one would want to in pure SQL – that is no artificial surrogate key and application data as a compound key – you can definitely do it. However, using JPA makes this a little bit messier than you might want it to be.
Thanks to @JB Niznet for his great input, you can definitely also do this by introducing a surrogate key as he suggests. The solution below just removes that need and as far as I can tell has no drawbacks.