Объекты equals(), hashCode() и toString(). Как правильно их реализовать?
Я реализую equals()
, hashCode()
и toString()
моих сущностей, используя все доступные поля в bean.
Я получаю Lazy init Exception в интерфейсе, когда я пытаюсь сравнить равенство или когда я печатаю состояние obj. Это потому, что некоторый список в объекте может быть ленивым инициализирован.
Мне интересно, какой правильный способ реализовать equals()
и toString()
для объекта сущности.
Ответы
Ответ 1
equals()
и hashCode()
должны быть реализованы с помощью бизнес-ключ - то есть набор свойств, которые однозначно идентифицируют объект, но не являются его автогенерированным идентификатором.
в toString()
вы можете поместить любую информацию интересную - например, все поля.
Используйте свои IDE (Eclipse, NetBeans, IntelliJ), чтобы генерировать все это для вас.
Чтобы избежать LazyInitializationException
, независимо от того, есть ли в equals()
или в вашем представлении (jsp), вы можете использовать OpenSessionInView
.
Ответ 2
Когда вы реализуете методы equals и hashCode для объектов Hibernate, важно
- Используйте getters вместо прямого доступа к свойствам класса.
- Не сравнивать классы объектов, а использовать
instanceof
вместо
Дополнительная информация:
fooobar.com/questions/27/...
Документация для спящего режима: Equals и HashCode
Изменить: те же правила о недоступности свойств класса напрямую применяются также к методу toString - только с использованием геттеров гарантируется, что информация, содержащаяся в классе, возвращается.
Ответ 3
- Если два объекта равны, они должны иметь тот же хэш-код.
- Метод equals() по умолчанию проверяет, ссылаются ли две ссылки на один и тот же экземпляр in-memory на кучу Java
Вы можете полагаться на идентификатор Entity, чтобы сравнить свой Entity с помощью equals
public boolean equals(Object o) {
if(o == null)
return false;
Account account = (Account) o;
if(!(getId().equals(account.getId())))
return false;
return true;
}
Но, что происходит, когда у вас есть нерезидентная сущность. Он не будет работать, потому что его идентификатор не назначен.
Итак, посмотрим, что говорит о Java Persistence with Hibernate Book
Бизнес-ключ - это свойство или некоторая комбинация свойств , которая уникальна для каждого экземпляра с тем же идентификатором базы данных.
So
Это естественный ключ, который вы бы использовали, если вместо этого вы использовали суррогатный первичный ключ.
Итак, предположим, что у вас есть User Entity, а его естественные ключи - firstName и lastName (по крайней мере, его/ее firstName и lastName часто не меняются). Таким образом, он будет реализован как
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;
}
Все отлично! Я использую даже с объектами Mock, такими как репозитории, службы и т.д.
И о методе toString(), как сказал @Bozho, вы можете поместить любую информацию в интересную. Но помните, что некоторые веб-фреймворки, такие как Wicket и Vaadin, используют этот метод для отображения своих значений.
Ответ 4
Помимо того, что говорили другие, я также думаю, что объект Hibernate все еще должен быть привязан к сеансу, чтобы получить ленивую информацию. Без подключения к базе данных эти списки не могут быть загружены:)
Ответ 5
Моя реализация toString() для объектов Hibernate заключается в следующем:
@Override
public String toString() {
return String.format("%s(id=%d)", this.getClass().getSimpleName(), this.getId());
}
Каждый подкласс моей AbstractEntity (выше) переопределяет этот метод при необходимости:
@Override
public String toString() {
return String.format("%s(id=%d, name='%s', status=%s)",
this.getClass().getSimpleName(),
this.getId(),
this.getName(),
this.getStatus());
}
Для hashCode() и equals() помните, что Hibernate часто использует прокси-классы. Поэтому мой equals() обычно выглядит следующим образом:
@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;
}
И как уже говорили другие... будьте осторожны при доступе к ленивым загруженным свойствам! Простой файл toString() или даже log.debug(entity) может вызвать огромную активность, если каскадировать несколько ленивых загружаемых объектов и свойств.
Ответ 6
Мы реализуем equals() и hashCode() в нашем суперклассе. Это работает безупречно, особенно в Картах и списках и т.д.
Это было правильно, поскольку мы делаем много транзитивной настойчивости.
равно():
/**
* 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());
}
хэш-код():
/**
* 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();
}
Ответ 7
Это, вероятно, лучший и самый простой способ сделать это:
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();
}
Ответ 8
Если вам удалось переопределить equals() для объектов Hibernate, убедитесь, что вы выполняете его контракты: -
- Симметрия
- REFLECTIVE
- ПЕРЕХОДНЫЕ
- CONSISTENT
- NON NULL
И переопределить hashCode
, поскольку его контракт зависит от реализации equals
.
Джошуа Блох (разработчик Framework Collection) строго придерживается этого правила
- item 9: Всегда переопределять hashCode, когда вы переопределяете equals
Есть серьезные непреднамеренные последствия, когда вы не следуете его контракту. Например, List.contains(Object o)
может возвращать неправильное значение boolean
, поскольку общий контракт не выполняется.
Ответ 9
- Если у вас есть бизнес-ключ, вы должны использовать это для
equals
/hashCode
.
- Если у вас нет бизнес-ключа, вы не должны оставлять его с реализацией по умолчанию
Object
equals и hashCode, потому что это не работает после вас merge
и entity.
-
Вы можете использовать идентификатор объекта, как предлагается в этом сообщении. Единственный улов - вам нужно использовать реализацию 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 getId() != null &&
Objects.equals(getId(), book.getId());
}
@Override
public int hashCode() {
return 31;
}
//Getters and setters omitted for brevity
}