views:

586

answers:

6

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.

+2  A: 

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.

Bozho
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
for the lazy init exception, search SO, or ask another question. It's a whole different story
Bozho
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
in other words in toString() I have whatever information is interesting (all fields) this is why I'm having problems
spike07
@spike07 see my update
Bozho
+3  A: 

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.

simon
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
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
A: 

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
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
A: 

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
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
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
+1  A: 
  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 instance of 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(o.getFirstName())))
        return false;

    if(!(getLastName().equals(o.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.

Arthur Ronald F D Garcia
Hi Arthur. I edited your question as per http://meta.stackoverflow.com/questions/2950/should-hi-thanks-and-taglines-and-salutations-be-removed-from-posts.
Pascal Thivent
@Pascal Thivent Thank you. Good to know!
Arthur Ronald F D Garcia
A: 

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