gpt4 book ai didi

java - 具有集合的JPA实体针对分离成员上的contains方法返回false

转载 作者:塔克拉玛干 更新时间:2023-11-01 22:21:52 24 4
gpt4 key购买 nike

我有两个JPA实体类,组和用户

Group.java:

@Entity
@Table(name = "groups")
public class Group {

@Id
@GeneratedValue
private int id;


@ManyToMany
@JoinTable(name = "groups_members", joinColumns = {
@JoinColumn(name = "group_id", referencedColumnName = "id")
}, inverseJoinColumns = {
@JoinColumn(name = "user_id", referencedColumnName = "id")
})
private Collection<User> members;


//getters/setters here

}

User.java:
@Entity
@Table(name = "users")
public class User {

private int id;
private String email;

private Collection<Group> groups;

public User() {}

@Id
@GeneratedValue
public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

@Column(name = "email", unique = true, nullable = false)
public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "groups_members", joinColumns = {
@JoinColumn(name = "user_id")
}, inverseJoinColumns = {@JoinColumn(name = "group_id")})
public Collection<Group> getGroups() {
return groups;
}

public void setGroups(Collection<Group> groups) {
this.groups = groups;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;

User user = (User) o;

if (id != user.id) return false;
return email.equals(user.email);
}

@Override
public int hashCode() {
int result = id;
result = 31 * result + email.hashCode();
return result;
}
}

我尝试为一个成员组的组运行以下代码段,其中 group是刚从JpaRepository中检索到的实体,而 user是该组和独立实体的成员。
            Collection<User> members = group.getMembers();
System.out.println(members.contains(user)); //false
User user1 = members.iterator().next();
System.out.println(user1.equals(user)); //true

经过一些调试后,我发现 User.equals()调用期间调用了 .contains(),但是Hibernate集合中的用户具有空字段,因此 .equals()评估为false。

那么,为什么这么奇怪?在这里调用 .contains()的正确方法是什么?

最佳答案

这个难题有两个方面。首先,@ManyToMany关联的获取类型为LAZY。因此,在您的组中,members字段采用延迟加载。当使用延迟加载时,Hibernate将使用对象的代理仅在访问它们时进行实际加载。实际的集合很可能是PersistentBagPersistentCollection的某种实现(忘记了,并且目前还无法访问Hibernate javadocs),这在您的背后做了一些魔术。

现在,您可能想知道,当您调用group.getMembers()时,是否不应该获取实际的集合并能够使用它而不必担心其实现?是的,但是仍然需要注意延迟加载。您会看到,集合中的对象本身是代理,它们最初仅加载其标识符,而未加载其他属性。只有在访问了这样的属性后,整个对象才会被初始化。这使Hibernate可以做一些聪明的事情:

  • 它使您无需加载所有内容即可检查集合的大小。
  • 您可以仅获取集合中对象的标识符(主键),而无需查询整个对象。外键通常在加载父对象的联接时非常高效,并且用于很多事情,例如检查持久性上下文中是否已知对象。
  • 您可以在集合中获取特定的对象并进行初始化,而无需初始化集合中的每个对象。尽管这可能会导致许多查询(“N + 1问题”),但它也可以确保通过网络发送并加载到内存中的数据不超过所需数量。

  • 下一个难题是在 User类中,您使用了属性访问而不是字段访问。您的注释位于getter而不是字段上(例如 Group中的注释)。也许情况已经改变了,但是至少在某些较旧的Hibernate版本中,通过代理仅获取标识符只能与属性访问一起使用,因为代理通过替换方法进行操作,但不能规避字段访问。

    所以发生的是,在您的equals方法中,此部分可能工作正常: if (id != user.id) return false;
    ...但这不是:返回 email.equals(user.email);
    您可能还得到了一个nullpointer异常,但还没有发生,contains方法以其集合项作为参数调用所提供的对象(您的已填充,分离的用户)上的equals。反之,则可能导致空指针。这是难题的最后一部分。您直接在此处使用字段,而不是使用getter来发送电子邮件,因此您不会强制Hibernate加载数据。

    因此,您可以执行一些实验。我会自己尝试的,但是在这里已经很晚了,我必须走了。让我知道结果如何,看看我的答案是否正确,并使其对以后的访问者更有用。
  • 通过将JPA / Hibernate批注放在字段上,将User中的属性访问更改为字段访问。除非在最近的版本中对此进行了更改,否则它应导致在访问集合时初始化User实例的所有属性,而不仅仅是填充标识符的代理。但这可能不再起作用。
  • 尝试首先通过迭代器从集合中获取该user1实例。看到您没有进行显式属性访问的方式,我强烈怀疑在集合上获取迭代器并从中获取元素也会强制对该元素进行初始化。例如,列表的contains的Java实现调用的indexOf仅通过内部数组,但是不调用get这样的任何可触发初始化的方法。
  • 尝试在equals方法中使用吸气剂,而不是直接进行字段访问。我发现在处理JPA时,最好始终使用getter和setter,甚至对于类本身中的方法,也可以避免此类问题。作为实际的解决方案,这可能是最可靠的方法。不过,请务必处理email可能为null的情况。

  • JPA在您的背后做了一些疯狂的魔术,并试图使它对您几乎不可见,但有时它会再次咬住您。如果有时间的话,我将在Hibernate源代码中进行更多的挖掘并进行一些实验,但是稍后我可能会再次进行讨论以验证上述声明。

    关于java - 具有集合的JPA实体针对分离成员上的contains方法返回false,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43548142/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com