Подтвердить что ты не робот

Объекты equals(), hashCode() и toString(). Как правильно их реализовать?

Я реализую equals(), hashCode() и toString() моих сущностей, используя все доступные поля в bean.

Я получаю Lazy init Exception в интерфейсе, когда я пытаюсь сравнить равенство или когда я печатаю состояние obj. Это потому, что некоторый список в объекте может быть ленивым инициализирован.

Мне интересно, какой правильный способ реализовать equals() и toString() для объекта сущности.

4b9b3361

Ответ 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
    }