Entities equals(), hashCode() and toString(). How to correctly implement them?
I'm impleme开发者_如何学Pythonnting 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.
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
.
When you implement the equals and hashCode methods for Hibernate objects, it is important to
- Use getters instead of directly accessing the class properties.
- 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.
- If two objects are equal, they must have the same hashcode.
- 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.
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 :)
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.
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();
}
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();
}
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.
If you have a business key, then you should use that for
equals
/hashCode
.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 youmerge
and entity.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 }
精彩评论