32

I'm implementing equals(), hashCode() and toString() of my entities using all the available fields in the bean.

I'm getting some Lazy init Exception on the frontend when I try to compare the equality or when I print the obj state. That's because some list in the entity can be lazy initialized.

I'm wondering what's the correct way to for implementing equals() and toString() on an entity object.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
spike07
  • 809
  • 2
  • 12
  • 21
  • 2
    I had listed various strategies in this answer, but it was not related to lazy initialization. You might be interested http://stackoverflow.com/questions/2100226/nhibernate-what-are-the-best-practices-for-implementing-equality/2100478#2100478. – ewernli Mar 15 '10 at 14:04
  • See also http://stackoverflow.com/questions/4890133/is-it-really-worth-implementing-tostring-for-entity-classes – Raedwald Aug 05 '14 at 11:27

9 Answers9

20

equals() and hashCode() should be implemented using a business key - i.e. a set of properties that uniquely identify the object, but are not its auto-generated ID.

in toString() you can put whatever information is interesting - for example all fields.

Use your IDE (Eclipse, NetBeans, IntelliJ) to generate all these for you.

In order to avoid LazyInitializationException, no matter whether in equals() or in your view (jsp), you can use OpenSessionInView.

radoh
  • 4,554
  • 5
  • 30
  • 45
Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • I'm actually using IntellJ to generate the toString method. This is why I was having problems. Using the autogen the toString() call is cascaded to all the property fields, even Lists that are lazy initialized. I should use a subset of the fields I used in the business key I think. – spike07 Mar 15 '10 at 11:37
  • for the lazy init exception, search SO, or ask another question. It's a whole different story – Bozho Mar 15 '10 at 12:00
  • actually that's what I asked in the main question. The problems I'm having in equals() and toString are caused by an Entity that reference Lazy initialized fields. I know why the exception is thrown. I can't just put 'all fields' in toString as you stated. That's because a lazy Exception can be thrown. If on the frontend I've an Entity with a LazyList that I don't need, it looks odd to me to have to return a list I don't want just to be able to print out the entity state. – spike07 Mar 15 '10 at 12:07
  • in other words in toString() I have whatever information is interesting (all fields) this is why I'm having problems – spike07 Mar 15 '10 at 12:12
  • From the documentation at https://developer.jboss.org/wiki/EqualsAndHashCode : "Instead of using the database identifier for the equality comparison, you should use a set of properties for equals() that identify your individual objects." – simon Sep 29 '17 at 13:15
10

When you implement the equals and hashCode methods for Hibernate objects, it is important to

  1. Use getters instead of directly accessing the class properties.
  2. Not directly compare objects' classes, but use instanceof instead

More information:

Stackoverflow: overriding-equals-and-hashcode-in-java

Hibernate documentation: Equals and HashCode

Edit: the same rules about not accessing the class properties directly applies to toString method as well - only using the getters guarantees that the information really contained in the class is returned.

Community
  • 1
  • 1
simon
  • 12,666
  • 26
  • 78
  • 113
  • but still.. when I use getSomeLazyList() a Lazy Exception will be thrown by the proxy I think. I can't really reuse equals - toString in different part of my Application. They behaves differently depending if the object is still attached to the Hibernate Session or not.. – spike07 Mar 15 '10 at 11:50
  • Getting a LazyInitializationException is another problem - I was answering the part "I'm wondering what's the correct way to for implementing equals() and toString() on an Entity Obj" of your question. For the exception: I would check the mappings and how you access the object throwing the exception. – simon Mar 15 '10 at 12:25
7
  1. If two objects are equal, they must have the same hashcode.
  2. equals() method, by default, checks whether two references refer to the same in-memory instance on the Java heap

You can rely on Entity identifier to compare your Entity by using equals

public boolean equals(Object o) {
    if(o == null)
        return false;

   Account account = (Account) o;
   if(!(getId().equals(account.getId())))
       return false;

   return true;
}

But what happens when you have a non-persisted Entity. It will not work because its Identifier has not been assigned.

So Let's see what Java Persistence with Hibernate Book talks about it

A business key is a property, or some combination of properties, that is unique for each instance with the same database identity.

So

It is the natural key that you would use if you weren’t using a surrogate primary key instead.

So let's suppose you have a User Entity and its natural keys are firstName and lastName (At least, his/her firstName and lastName often does not change). So it would be implemented as

public boolean equals(Object o) {
    if(o == null)
        return false;

    if(!(o instanceof User))
        return false;

    // Our natural key has not been filled
    // So we must return false;
    if(getFirstName() == null && getLastName() == null)
        return false;

    User user = (User) o;
    if(!(getFirstName().equals(user.getFirstName())))
        return false;

    if(!(getLastName().equals(user.getLastName())))
        return false;

   return true;
}

// default implementation provided by NetBeans
public int hashcode() {
    int hash = 3;

    hash = 47 * hash + ((getFirstName() != null) ? getFirstName().hashcode() : 0)
    hash = 47 * hash + ((getLastName() != null) ? getLastName().hashcode() : 0)

    retrun hash;
}

It works fine! I use even with Mock objects like repositories, services etc

And about toString() method, as said by @Bozho, you can put whatever information is interesting. But remember some web frameworks, like Wicket and Vaadin, for instance, use this method to show its values.

Alex
  • 855
  • 7
  • 21
Arthur Ronald
  • 33,349
  • 20
  • 110
  • 136
  • Hi Arthur. I edited your question as per http://meta.stackexchange.com/questions/2950/should-hi-thanks-and-taglines-and-salutations-be-removed-from-posts. – Pascal Thivent Mar 16 '10 at 02:34
0

Apart from what the others said I also think a Hibernate object needs to still be attached to the session to retrieve lazy information. Without a database connection those lists can not be loaded :)

extraneon
  • 23,575
  • 2
  • 47
  • 51
  • exactly, so if I use them on my frontEnd and they are detached I can't really use equals() or toString() consistently around my app. I should return an entity that's correctly populated but when I don't need a List property and I want to printOut the entity state on the frontEnd I'll get an Exception. – spike07 Mar 15 '10 at 11:54
0

My implementation of toString() für Hibernate entities is the following:

@Override
public String toString() {
    return String.format("%s(id=%d)", this.getClass().getSimpleName(), this.getId());
}

Every subclass of my AbstractEntity (above) overrides that method if necessary:

@Override
public String toString() {
    return String.format("%s(id=%d, name='%s', status=%s)",
            this.getClass().getSimpleName(),
            this.getId(),
            this.getName(),
            this.getStatus());
}

For hashCode() and equals() keep in mind that Hibernate often uses proxy classes. So my equals() usally looks like this:

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;

    Class<AbstractEntity> c1 = Hibernate.getClass(this);
    Class<AbstractEntity> c2 = Hibernate.getClass(obj);
    if (!c1.equals(c2)) return false;

    final AbstractEntity other = (AbstractEntity) obj;
    if (this.getId() == null) {
        if (other.getId() != null) return false;
    }
    else if (!this.getId().equals(other.getId())) return false;

    return true;
}

And as others already stated... be careful with accessing lazy loaded properties! A simple toString() or even log.debug(entity) might cause huge activity if cascading into several lazy loaded objects and properties.

Daniel Bleisteiner
  • 3,190
  • 1
  • 33
  • 47
  • Aren't you creating an Hibernate dependency on your Entity if you use Hibernate.getClass..? If you pass your entities to other modules of your application you'll have hibernate as a transitive dependency as well – spike07 Mar 15 '10 at 12:02
  • Yes, that's true. We use other Hibernate features so we don't care in our project. The instanceof approach to compare classes can be tricky because it included subclasses - which might cause problems. – Daniel Bleisteiner Mar 15 '10 at 12:30
0

This is probably the best and most straightforward way to do it:

public String toString() {
    return "userId: " + this.userId + ", firstName: " + this.firstName + ", lastName: " + this.lastName + ", dir: " + this.dir + ", unit: " + this.unit + ", contractExpiryDate: " + this.contractExpiryDate + ", email: " + this.email + ", employeeNumber: " + this.employeeNumber + ", employeeType: " + this.employeeType + ", phone: " + this.phone + ", officeName: " + this.officeName + ", title: " + this.title + ", userType: " + this.userType;
}

public boolean equals(Object obj) {
[...]
return (toString().equals(other.toString()));
}

public int hashCode() {
return toString().hashCode();
}
doofus
  • 1
0

We implement equals() and hashCode() in our super class. This has work flawlessly, especially in Maps and Lists etc. This had to right as we do a lot transitive persistence.

equals():

/**
 * Compare two entity objects, following hibernate semantics for equality. Here we assume that
 * new objects are always different unless they are the same object. If an object is loaded from
 * the database it has a valid id and therefore we can check against object ids.
 *
 * @see com.dolby.persist.bean.EntityObject#equals(java.lang.Object)
 */
@SuppressWarnings("unchecked")
@Override
public final boolean equals(final Object object) {
    if (this == object) return true;
    if (object == null || this.getClass() != object.getClass()) return false;
    final AbstractModelObject<ID> other = (AbstractModelObject<ID>) object;
    if (this.getId() == null || other.getId() == null) return false;
    return this.getId().equals(other.getId());
}

hashCode():

/**
 * Returns an enttiy objects hashcode.
 * <p>
 * What we are doing here is ensuring that once a hashcode value is used, it never changes for
 * this object. This allows us to use object identity for new objects and not run into the
 * problems.
 * </p>
 * <p>
 * In fact the only case where this is a problem is when we save a new object, keep it around
 * after we close the session, load a new instance of the object in a new session and then
 * compare them.
 * </p>
 * <p>
 * in this case we get A==B but a.hashcode != b.hashcode
 * </p>
 * <p>
 * This will work in all other scenarios and don't lead to broken implementations when the
 * propety of the object are edited. The whole point in generating synthetic primary keys in the
 * first place is to avoid having a primary key which is dependant on an object property and
 * which therefore may change during the life time of the object.
 * </p>
 *
 * @see java.lang.Object#hashCode()
 */
@Override
public final synchronized int hashCode() {
    if (this.hashcodeValue == null) {
        if (this.getId() == null) {
            this.hashcodeValue = new Integer(super.hashCode());
        }
        else {
            final int generateHashCode = this.generateHashCode(this.getId());
            this.hashcodeValue = new Integer(generateHashCode);
        }
    }
    return this.hashcodeValue.intValue();
}
Kango_V
  • 1,720
  • 2
  • 15
  • 11
0

If you happened to override equals() on Hibernate entities, make sure you fulfill its contracts:-

  • SYMMETRY
  • REFLECTIVE
  • TRANSITIVE
  • CONSISTENT
  • NON NULL

And override hashCode, as its contract rely on equals implementation.

Joshua Bloch(designer of Collection framework) strongly this rule to be followed

  • item 9: Always override hashCode when you override equals

There are serious unintended effect when you don't follow its contract. For example List.contains(Object o) might return wrong boolean value as the general contract not fulfilled.

Awan Biru
  • 373
  • 2
  • 10
0
  1. If you have a business key, then you should use that for equals/hashCode.

  2. If you don't have a business key, you should not leave it with the default Object equals and hashCode implementations because that does not work after you merge and entity.

  3. You can use the entity identifier as suggested in this post. The only catch is that you need to use a hashCode implementation that always returns the same value, like this:

    @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 getId() != null && 
               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