【问题标题】:Hibernate/Spring Data not fetching collection in OneToMany mappingHibernate/Spring Data 未在 OneToMany 映射中获取集合
【发布时间】:2018-05-13 17:52:08
【问题描述】:

我在 Hibernate 中有一个一对多映射,当我尝试通过 spring JPARepository 将其从数据库中取出时,总是给我一个空集合。

我有一个像这样的用户类。

@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 + "]";
}

}

我的 FriendsList 实体(实际上是一个带有额外列的联结表)如下所示:

@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;
}

}

我的用户存储库就是这样的:

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

每当我获得一个用户并尝试通过 getFriendsList() 访问其好友列表时,它总是给我一个空集。我尝试过 FetchType.EAGER,以及 Hibernate.Initialize,还使用 ​​@Transactional 和 getFriendsList().size() 初始化旧方法。我相信 getFreindsList() 的结果是一个 HashSet 而不是某种休眠代理集这一事实表明休眠甚至没有尝试延迟初始化它?

hibernate生成的SQL如下:

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=?;

如您所见,hibernate 确实在尝试查询friendslist 表,但数据并没有进入集合。我看到的唯一不同寻常的是,我们在friendslist中有2个相同的select语句,我们通常期望1个。也许第二个有不同的用户ID并且覆盖了第一个?但我不知道第二个选择是从哪里来的。

【问题讨论】:

    标签: java spring hibernate spring-mvc jpa


    【解决方案1】:

    这似乎是一个棘手的问题,我的解释如下:

    FiendsList 实体有一个由userIdfriendId 列组成的复合主键。他们都引用了User.userId 列:

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

    在 User 实体上,您只需将映射放入 FiendList.user 字段:

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

    问题是,hibernate 知道要做什么是不够的,因为它需要两列才能正确获取 FriendsList 实体(因为它具有复合 pk)。

    您可能需要在获取 User 实体后执行附加查询,以便通过将检索到的 User.userId 值作为 FriendListPk 的一部分传递来获取 FriendList

    @Repository
    public interface FriendListDao extends JpaRepository<FriendList, FriendListPK>{
        ... // use one of the find methods to get by pk.
    }
    

    【讨论】:

    • 我知道只有复合键的一部分不足以唯一标识 FriendsList 实体,但由于它是 OneToMany 并且我返回一个集合,而不是单个 FriendsList实体,不应该休眠返回与我提供的一列匹配的所有 FriendsList 实体,而不是尝试找到唯一匹配项?
    【解决方案2】:

    哎呀,我真傻,原来我只是在 setter 中遗漏了一个“s”。

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

    “friendlist”参数实际上应该是“friendslist”。 Hibernate 正在获取数据,但是当它试图将其分配给字段时,现有的实例变量被默默地分配给它自己,而不是 setter 的参数。

    【讨论】:

      猜你喜欢
      • 2014-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多