72

I want to check if entity is in a Collection member (@OneToMany or @ManyToMany) of another entity:

if (entity2.getEntities1().contains(entity1)) { }
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
j2j
  • 821
  • 2
  • 9
  • 7
  • To elaborate on this question's motivation: [`Collection.contains(JpaEntity someObject)`](http://docs.oracle.com/javase/7/docs/api/java/util/Collection.html#contains(java.lang.Object)) requires a reasonable `JpaEntity.equals(...)` method. – Abdull Feb 10 '13 at 14:51
  • See also http://stackoverflow.com/a/39827962/548473 (spring-data-jpa implementation) – Grigory Kislin Oct 03 '16 at 09:07

6 Answers6

128

Not necessarily. There are three options:

  • don't override - thus you will be working with instances. This is fine in cases when you are working with the collections with only entities that are attached to the session (and hence guaranteed to be the same instance). This is (for me) the preferred way in many cases, because it requires less code and less consideration when overriding

  • override hashCode() and equals() with a business key. That may be a subset of properties that identify the entity. For example, for a User a good business key might be the username or the email. This is considered good practice.

  • override hashCode() and equals() using the ID field only. This is fine in some cases, especially if you have a manually-assigned identifier (like an UUID). It is also fine if your entity will never go into a collection. But for transient entities (with no identifier) that go into collections, it causes problems, so careful with this option. As seanizer noted - you should avoid it. Generally, always, unless you are really aware of what you are doing (and perhaps documenting it)

See this article for more details. Also note that equals()and hashCode() are tied and should be implemented both with exactly the same fields.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • ok, I added this in Entity1 and now it works: public boolean equals(Object other){ if (this.getClass().isInstance(other)){ return this.id == ((UserEntity)other).id; } else { return false; } } public int hashCode() { return id; } – j2j Dec 08 '10 at 14:48
  • 1
    so you chose the least preferable option ;) Watch out for side-effects. – Bozho Dec 08 '10 at 14:52
  • 1
    Every time I start a JPA project I keep ending on this question, thanks for clearing my mind again and again :) – eliocs Oct 06 '11 at 15:55
  • 1
    I recommend using [Lombok](http://projectlombok.org/), it has a nice `@EqualsAndHashcode` annotation that creates these methods for you. – Elias Dorneles Jul 29 '12 at 04:17
  • for me, removeAll() doesn't need equals() for an Arraylist of entities – Prabhat Gaur Dec 15 '20 at 11:55
23

Yes, you should!

If you don't override the default Java.lang.Object equals and hashCode implementation:

@Entity(name = "Book")
public class Book implements Identifiable<Long> {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    //Getters and setters omitted for brevity
}

the merge operation will return a different object instance and the equality contract is going to be broken.

The best way is to use a business key, like this:

@Entity
public class Book implements Identifiable<Long> {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    @NaturalId
    private String isbn;
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Book)) return false;
        Book book = (Book) o;
        return Objects.equals(getIsbn(), book.getIsbn());
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(getIsbn());
    }
 
    //Getters and setters omitted for brevity
}

You can also use the identifier for equality, but mind that the hashCode implementation should always return the same value, which for entities is not really a problem since you don't fetch many entities per DB transaction, as otherwise, the cost of fetching data is orders of magnitude larger than the single-bucket HashMap penalty imposed by using a fixed hashCode:

@Entity
public class Book implements Identifiable<Long> {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String title;
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Book)) return false;
        Book book = (Book) o;
        return Objects.equals(getId(), book.getId());
    }
 
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
 
    //Getters and setters omitted for brevity
}
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
13

Yes, you should define corresponding equals() and hashcode() methods, but you should NEVER let the id be part of either. (See this recent answer of mine in a similar question)

Community
  • 1
  • 1
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • I have used from time to time equals and hashcode overridden with the id only, and it has worked fine. True I considered the usecases, and the fact that I won't be working with transient entities. So I wouldn't say exactly 'never' :) – Bozho Dec 08 '10 at 14:23
  • I *would* say never, because not being able to compare transient and attached entities is a no-go for me, but I understand the simplicity of your approach (as long as you know the drawbacks) – Sean Patrick Floyd Dec 08 '10 at 14:27
  • That depends on when you assign your ids. If there are business keys, I prefer not to have any other ids at all. Like that the point is moot. – Joeri Hendrickx Dec 08 '10 at 14:49
  • @Joeri I usually don't assign IDs, the JPA provider does. – Sean Patrick Floyd Dec 08 '10 at 14:51
  • @Joeri but I agree, business keys as IDs is a nice concept – Sean Patrick Floyd Dec 08 '10 at 14:52
10

There is information in the Hibernate documentation on this topic.

Vineet Reynolds
  • 76,006
  • 17
  • 150
  • 174
Cid54
  • 406
  • 2
  • 7
7

We tend to let IDE generate hashCode() and equals() for us. Be careful though. When you generate those methods for JPA Entities. Some versions of equals() checks for class identity

// ... inside equals() - wrong approach for Entities (cause of generate proxies)
if (o == null || this.getClass() != o.getClass()) {
        return false;
}
// ...

This would break your collections with some JPA libraries as those libraries create proxies to your entities (subclasses), like for example MyGreatEntity_$$_javassist_7 in Hibernate.

In Entities always allow subclasses in equals().

zbig
  • 3,830
  • 2
  • 29
  • 37
2

That's the only way. You may want to try Pojomatic library which does the hard job for you.

Boris Pavlović
  • 63,078
  • 28
  • 122
  • 148