Question

I am trying to design some kind of user to user relationship, such as "user A follows user B" and "User A wants to be User B's friend".

I have a User class, and the way it is designed looks like this:

@Entity
public class User{
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    List<User> followers;
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    List<User> following;
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    List<User> friendRequests;
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    List<User> requesting;
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    List<User> friends;

}

I am running into two problems:

  • Hibernate is giving me cannot simultaneously fetch multiple bags problem
  • I have looked up online, people said to remove FetchType.EAGER or change it to Set instead of List, but that resulted me Field doesn't have a default value

I have a feeling that the relationship is not defined properly, and also I should be seeing more tables, because right now, I only see User table, and User_User table.


Update

The following creates 3 table, friends, followers, and requesters. Is this somewhat optimized compared to 5 tables? And are there any advantage to this in comparison with what Mr.J4mes suggested?

@ManyToMany
@JoinTable(name = "followers", joinColumns = @JoinColumn(name = "followerId"), inverseJoinColumns = @JoinColumn(name = "userId"))
private List<User> followers;
@ManyToMany
@JoinTable(name = "followers", joinColumns = @JoinColumn(name = "userId"), inverseJoinColumns = @JoinColumn(name = "followerId"))
private List<User> following;
@ManyToMany
@JoinTable(name = "friends", joinColumns = @JoinColumn(name = "userId"), inverseJoinColumns = @JoinColumn(name = "friendId"))
private List<User> friends;
@ManyToMany
@JoinTable(name = "requesters", joinColumns = @JoinColumn(name = "requesterId"), inverseJoinColumns = @JoinColumn(name = "userId"))
private List<User> friendRequests;
@ManyToMany
@JoinTable(name = "requesters", joinColumns = @JoinColumn(name = "userId"), inverseJoinColumns = @JoinColumn(name = "requesterId"))
private List<User> requesting;
Was it helpful?

Solution

First of all, to implement your feature, you should use @ManyToMany instead of @OneToMany. It should look like this:

@Entity
public class User implements Serializable {
   @ManyToMany(mappedBy="followers")
   @JoinTable(name="followingTable")
   private Set<User> following;
   @ManyToMany
   @JoinTable(name="followerTable")
   private Set<User> followers;
   @ManyToMany(mappedBy="friendRequests")
   @JoinTable(name="requestingTable")
   private Set<User> requesting;
   @ManyToMany
   @JoinTable(name="friendRequestTable")
   private Set<User> friendRequests;
   @ManyToMany
   private Set<User> friends;
}

Your relationships looks like bidirectional ones to me. If you use @OneToMany, it means that C has 2 followers A and B = A and B only follows C. However, the fact is that one person can follows many people and one person can be followed by many people. In other words, A and B can also follow D.

Besides, you shouldn't use cascadeType.ALL at all. That cascade policy means that if one user deletes his account and you delete the corresponding entry in the database, all of his friends, etc. will also be deleted.

OTHER TIPS

Hibernate will only generate one table for the User entity, and I'm assuming a cross reference table for the User to User relationship. Workaround to having different tables might be having different entities setup for the different relationships i.e.

User Entity

@OneToMany
List<Follower> followers;

Follower Entity

@Entity
class Follower
...
@ManyToOne
User user;

@Id
@Generated
int id;

As for the EAGER fetching, you might actually want all of those to be LAZY, due to the fact that depending on how your database is set up, all of those eager loads could be quite expensive. Only when the user wants to load the users followers, would I want to fetch them.

Separate the User from Friends try to make a table with at least 2 columns that maps the User_ID to all his Friends_IDs which is basically a reference to User_ID

Also EAGER will load ALL the data as soon as you call it the first time. Means it will load all the Users and their Friends. Loading friends should be LAZY, i.e. only loaded when you need them.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top