Java – JPA entities with collections return false to the contains method on detached members

I have two JPA entity classes, group and user

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

I try to run the following code snippet for a group with one member, where group is the entity just retrieved from the jparepository, and user is a member of the group and the detached entity

Collection<User> members = group.getMembers();
            System.out.println(members.contains(user)); //false
            User user1 = members.iterator().next();
            System.out.println(user1.equals(user)); //true

After some debugging, I found that User. Is called during the contains() call Equals(), but the user in the hibernate collection has an empty field, so Equals() is evaluated as false

So why is it so strange and called here What is the correct method for contains()?

Solution

The problem has several parts First, the acquisition type associated with @ manytomany is lazy Therefore, in your group, the member field uses deferred loading When deferred loading is used, hibernate will use the object's proxy to perform the actual load only when they are accessed The actual collection is likely to be some implementations of persistentbag or persistentcollection (what have you forgotten, and hibernate JavaDocs doesn't seem to be accessible at present). These implementations have some magic behind you

Now, you might want to know when you call group When getmembers (), should you get the actual collection and be able to use it without worrying about its implementation? Yes, but there is still a problem of delayed loading You see, the objects in the collection are proxies themselves. They only load their identifiers at first, but they don't load other properties The full object is initialized only when such a property is accessed This allows hibernate to do something smart:

>It allows you to check the size of the collection without having to load everything. > You can only get the identifier (primary key) of the object in the collection, not the whole object When the parent object is loaded using a connection, foreign keys are usually very effective and are used for many things, such as checking whether the object is known in the persistence context. > Instead of initializing each object in the collection, you can get a specific object in the collection and initialize the collection Although this may lead to many queries ("n 1 problems"), it can also ensure that the data sent over the network does not exceed the required data and is loaded into memory

The next problem is that in your user class, you use property access instead of field access Your comments are in getters instead of fields (such as group) Perhaps this has changed, but at least in some old versions of hibernate, only obtaining identifiers through agents can only be accessed using attributes, because agents operate through alternative methods, but can not bypass field access

So it will happen in your equals method. This part may work normally: if (ID! = user. ID) returns false;

... but this is not: return email equals(user.email);

You may also get a nullpointer exception. This does not happen, that is, the contains method calls the same collection entry as the parameter in the provided object (your fill, separate user) This in turn can lead to nullpointer This is the last part of the puzzle You use these fields directly here instead of getters to get email, so you don't force hibernate to load data

So here are some experiments you might do I'll try it myself, but it's late now. I must go Let me know what the result is, see if my answer is correct, and make it more useful to future visitors

>Change the attribute access permission in user to field access permission by placing JPA / Hibernate annotation on the field Unless changed in the latest version, it should initialize all the properties of the user instance when accessing the collection, not just the agent populated with identifiers However, this may no longer work. > First, try to get the user1 instance from the collection through the iterator Looking at how you do not have explicit attribute access, I strongly doubt that getting an iterator on a collection and getting an element from it will also force the initialization of the element For example, the Java implementation containing list calls indexof, which only passes through the internal array, but does not call any get method that may trigger initialization. > Try using getters in the equals method instead of direct field access I find it best to always use getters and setters when dealing with JPA, even if it is the method of the class itself As a practical solution, this is probably the most powerful way However, please make sure to handle the case where the email may be empty

JPA does some crazy magic behind you and tries to make it almost invisible to you, but sometimes it comes back and bites you If I have time, I will mine more content in Hibernate source code and run some experiments, but I may visit again later to verify the above statement

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>