0

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;
HeavenAgain
  • 435
  • 1
  • 6
  • 14

3 Answers3

1

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.

Mr.J4mes
  • 9,168
  • 9
  • 48
  • 90
  • `Hibernate: insert into User_User (requesting_id, friendrequests_id) values (?, ?) 2012-01-12 00:52:04,900 WARN [SqlExceptionHelper] SQL Error: 1364, SQLState: HY000 2012-01-12 00:52:04,901 ERROR [SqlExceptionHelper] Field 'following_id' doesn't have a default value` This is the problem I am getting. I think the user_user table have 5 columns, which is not necessary. And when I only need to update either request or following, the other one is not set, which throws this problem. – HeavenAgain Jan 12 '12 at 05:55
  • @user1129335 if your relationships are really bidirectional as I mentioned, you don't have a choice. To solve your problem, I updated my answer with `@JoinTable` annotation. This annotation will help you create 5 different tables for your relationships. – Mr.J4mes Jan 12 '12 at 06:31
  • There are two things I want to ask, the effect of `cascadeType.ALL` you mentioned sounds like a desired effect, because if a user deletes his account, then we don't really want to have a ghost user? But that's besides the point. I came across this [post](http://stackoverflow.com/questions/1656113/hibernate-many-to-many-association-with-the-same-entity), it seems like I need another collection for friendOf. Question, is it really necessary to have a bidirectional relationship? (Also, instead of creating 5 tables like you have, this can be simplified to three tables? using joinColumn) – HeavenAgain Jan 12 '12 at 06:42
  • If the other post is true, then I don't see the need for having 2 collections, friends and friendOf, one of them (friendOf) will probably never be used. Because I don't need "direction" in friends, like, user A is a friend of user B. I just want to generalize it. – HeavenAgain Jan 12 '12 at 06:54
  • I don't think it's a desirable effect. If one user deletes his account, all of his friends' accounts will also be deleted as well. Besides, I don't think you need a collection for `friendOf`. If you are my friend, I am, of course, your friend. In other words, you only need to know who your friends are. You don't need to know you are friend of who. – Mr.J4mes Jan 12 '12 at 06:55
  • For the directional problem, it all depends on your desired functionality. If you want to show to a user who is following him, you need the `followers` collection. If you want to show a user who he is following, you need the `following` collection. These 2 collections are 2 direction of the same relationship. In other words, this is bidirectional relationship. – Mr.J4mes Jan 12 '12 at 06:57
  • But because the follower and following are the same thing, just inversed. So they could be put on the same table, right? I will make an update to show you what I mean. – HeavenAgain Jan 12 '12 at 07:00
  • The problem is that if you are following someone, he/she may not follow you as well. If you put them in the same table, you may get the same exception that you mentioned earlier. – Mr.J4mes Jan 12 '12 at 07:05
  • Why have you made a followersTable & a followingTable?! Wouldn't it make sense to have 1 table which holds all followers...E.g user 1 followers user 2, `| userId - 1 | followerId - 2|` and if user 2 wanted to follow user 1 we'd just insert `| userId - 2 | followerId - 1|`?! So if we wanted to get all followers for user 1 we'd just do `select * from followers where userId = 1 ` – James111 Jul 24 '16 at 06:57
0

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.

dardo
  • 4,870
  • 4
  • 35
  • 52
0

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.

mohdajami
  • 9,604
  • 3
  • 32
  • 53
  • That is what I am thinking, however, how do I separate the table in my case, because it is all User references. – HeavenAgain Jan 12 '12 at 05:46