Search code examples
javaspringhibernatespring-mvcjpa

Hibernate/Spring Data not fetching collection in OneToMany mapping


I have a one to many mapping in Hibernate that when I try to get it out of the database through a spring JPARepository, always gives me an empty collection.

I have a User class that looks like this.

@Indexed
@Entity
@Table(name="usr", indexes = {@Index(columnList = "email", unique = true)})
public class User{
    private Long userId;
    private String email;
    private String firstname;
    private String lastname;
    private String phone;
    private String passwordHash;

    private Set<PollOption> votes;
    private Set<FriendsList> friendsList; 

//no args constructor
    public User() {
        votes = new HashSet<>();
        friendsList = new HashSet<>();
    }

/**
 * Gets the value of id
 *
 * @return the value of id
 */
@Id
@GenericGenerator(name = "userautoinc", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
          parameters = {
          @Parameter(name = "sequence_name", value = "userautoinc"),
          @Parameter(name = "optimizer", value = "hilo"),
          @Parameter(name = "initial_value", value = "1"),
          @Parameter(name = "increment_size", value = "1") }
)
@GeneratedValue(generator = "userautoinc")
@Column(name="userid")
    public Long getUserId() {
    return this.userId;
}

/**
 * Sets the value of id
 *
 * @param argId Value to assign to this.id
 */
public void setuserId(final Long argId) {
    this.userId = argId;
}

/**
 * Gets the value of email
 *
 * @return the value of email
 */
@Field
@NaturalId
@Column(name="email", nullable = false)
    public String getEmail() {
    return this.email;
}

/**
 * Sets the value of email
 *
 * @param argEmail Value to assign to this.email
 */
public void setEmail(final String argEmail) {
    this.email = argEmail;
}

/**
 * Gets the value of firstname
 *
 * @return the value of firstname
 */
@Field
@Column(name="firstname", nullable = false)
public String getFirstname() {
    return this.firstname;
}

/**
 * Sets the value of firstname
 *
 * @param argFirstname Value to assign to this.firstname
 */
public void setFirstname(final String argFirstname) {
    this.firstname = argFirstname;
}

/**
 * Gets the value of lastname
 *
 * @return the value of lastname
 */
@Field
@Column(name="lastname", nullable = false)
    public String getLastname() {
    return this.lastname;
}

/**
 * Sets the value of lastname
 *
 * @param argLastname Value to assign to this.lastname
 */
public void setLastname(final String argLastname) {
    this.lastname = argLastname;
}

/**
 * Gets the value of phone
 *
 * @return the value of phone
 */
@Column(name="phone", nullable = true)
public String getPhone() {
    return this.phone;
}

/**
 * Sets the value of phone
 *
 * @param argPhone Value to assign to this.phone
 */
public void setPhone(final String argPhone) {
    this.phone = argPhone;
}

/**
 * Gets the value of passwordHash
 *
 * @return the value of passwordHash
 */
@Column(name="passwordhash", nullable = false)
public String getPasswordHash() {
    return this.passwordHash;
}

/**
 * Sets the value of passwordHash
 *
 * @param argPasswordHash Value to assign to this.passwordHash
 */
public void setPasswordHash(final String argPasswordHash) {
    this.passwordHash = argPasswordHash;
}


@ManyToMany(mappedBy = "voters", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Set<PollOption> getVotes(){
    return this.votes;
}

public void setVotes(Set<PollOption> votes){
    this.votes = votes;
}

@OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Set<FriendsList> getFriendsList() {
    return this.friendsList;
}

public void setFriendsList(Set<FriendsList> friendList) {
    this.friendsList = friendsList;
}

//To String Method
@Override
public String toString() {
    return "User [userId=" + userId + ", email=" + email + ", firstname=" + firstname + ", lastname=" + lastname
    + ", phone=" + phone + ", passwordHash=" + passwordHash + "]";
}

}

My FriendsList Entity(really a junction table with extra columns) looks like this:

@Entity
@Table(name = "friendslist")  
public class FriendsList{


    private FriendsListPK friendsListPK;

    private User user;
    private User friend;
    private FriendsListStatus status;

    @Embeddable
    public static class FriendsListPK implements Serializable{

        private Long userId;
        private Long friendId;

        private FriendsListPK(){
        }

        private FriendsListPK(Long userId, Long friendId){
            this.userId = userId;
            this.friendId = friendId;
        }

        @JoinColumn(name = "userid", referencedColumnName = "userid")
        public Long getUserId(){
             return this.userId;
        }

        public void setUserId(Long userId){
            this.userId = userId;
        }

        @JoinColumn(name = "friendid", referencedColumnName = "userid")
        public Long getFriendId(){
             return this.friendId;
        }

        public void setFriendId(Long friendId){
            this.friendId = friendId;
        }

        @Override
        public boolean equals(Object o){
            boolean ret = true;
            if (o == null || this.getClass() != o.getClass()){ 
                ret = false;
            } else{
            FriendsListPK that = (FriendsListPK) o;
            ret = this.userId.equals(that.getUserId()) &&
            this.friendId.equals(that.getFriendId());
            }
            return ret;
        }

        @Override
        public int hashCode(){
            return Objects.hash(this.userId, this.friendId);
        }

    }

/**
 * Gets the value of friendsListPK
 *
 * @return the value of friendsListPK
 */
@EmbeddedId
public FriendsListPK getFriendsListPK() {
   return this.friendsListPK;
}

/**
 * Sets the value of friendsListPK
 *
 * @param argFriendsListPK Value to assign to this.friendsListPK
 */
public void setFriendsListPK(FriendsListPK argFriendsListPK) {
    this.friendsListPK = argFriendsListPK;
}

/**
 * Gets the value of status
 *
 * @return the value of status
 */
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "statusid", referencedColumnName = "statusid", nullable = false)
public FriendsListStatus getStatus() {
    return this.status;
}

/**
 * Sets the value of status
 *
 * @param argStatus Value to assign to this.status
 */
public void setStatus(FriendsListStatus argStatus) {
    this.status = argStatus;
}


/**
 * Gets the value of user
 *
 * @return the value of user
 */
@ManyToOne
@MapsId("userId")
public User getUser() {
    return this.user;
}

/**
 * Sets the value of user
 *
 * @param argUser Value to assign to this.user
 */
public void setUser(User argUser) {
    this.user = argUser;
}

/**
 * Gets the value of friend
 *
 * @return the value of friend
 */
@ManyToOne
@MapsId("friendId")
public User getFriend() {
    return this.friend;
}

/**
 * Sets the value of friend
 *
 * @param argFriend Value to assign to this.friend
 */
public void setFriend(User argFriend) {
    this.friend = argFriend;
}

}

And my User Repository is simply this:

@Repository
public interface UserDao extends JpaRepository<User, Long>{
    public User findByEmail(String email);
}

Whenever I get a User and try to access its FriendsList through getFriendsList(), It always gives me an empty set. I've tried FetchType.EAGER, as well as Hibernate.Initialize and also initializing the old way with @Transactional and getFriendsList().size(). I believe the fact that the result of getFreindsList() is a HashSet and not some sort of hibernate proxy set suggests that hibernate isn't even trying to lazy-initialize it?

The SQL that hibernate generates is as follows:

select user0_.userid as userid1_11_, user0_.email as email2_11_,  user0_.firstname as firstname3_11_, user0_.lastname as lastname4_11_, user0_.passwordhash as passwordhash5_11_, user0_.phone as phone6_11_ from usr user0_ where user0_.email =?;
select friendslis0_.user_userid as user_userid2_4_0_, friendslis0_.friend_userid as friend_userid1_4_0_, friendslis0_.friend_userid as friend_userid1_4_1_, friendslis0_.user_userid as user_userid2_4_1_, friendslis0_.statusid as statusid3_4_1_, user1_.userid as userid1_11_2_, user1_.email as email2_11_2_, user1_.firstname as firstname3_11_2_, user1_.lastname as lastname4_11_2_, user1_.passwordhash as passwordhash5_11_2_, user1_.phone as phone6_11_2_, friendslis2_.statusid as statusid1_5_3_, friendslis2_.statusname as statusname2_5_3_ from friendslist friendslis0_ inner join usr user1_ on friendslis0_.friend_userid=user1_.userid inner join friendsliststatus friendslis2_ on friendslis0_.statusid=friendslis2_.statusid where friendslis0_.user_userid=?;
select friendslis0_.user_userid as user_userid2_4_0_, friendslis0_.friend_userid as friend_userid1_4_0_, friendslis0_.friend_userid as friend_userid1_4_1_, friendslis0_.user_userid as user_userid2_4_1_, friendslis0_.statusid as statusid3_4_1_, user1_.userid as userid1_11_2_, user1_.email as email2_11_2_, user1_.firstname as firstname3_11_2_, user1_.lastname as lastname4_11_2_, user1_.passwordhash as passwordhash5_11_2_, user1_.phone as phone6_11_2_, friendslis2_.statusid as statusid1_5_3_, friendslis2_.statusname as statusname2_5_3_ from friendslist friendslis0_ inner join usr user1_ on friendslis0_.friend_userid=user1_.userid inner join friendsliststatus friendslis2_ on friendslis0_.statusid=friendslis2_.statusid where friendslis0_.user_userid=?;

As you can see, hibernate IS indeed trying to query the friendslist table, but the data isn't going into the set. The only thing I see out of the ordinary is that we have 2 identical select statements into friendslist where we would normally expect 1. Perhaps the second one has a different userid and is overwriting the first one? But I have no idea where the second select is coming from.


Solution

  • Oops, silly me, turns out I was just missing an "s" in the setter.

    public void setFriendsList(Set<FriendsList> friendList) {
        this.friendsList = friendsList;
    }
    

    the "friendlist" argurment should in fact be "friendslist". Hibernate was fetching the data, but when it tried to assign it to the field, the existing instance variable was silently being assigned to itself rather than the argument of the setter.