Hibernate равно и прокси

У меня есть один BaseEntity, который абстрагирует свойство id и version. этот класс также реализует hashcode и equals на основе свойства PK (id).

BaseEntity{

    Long id;
    Long version; 

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    BaseEntity other = (BaseEntity) obj;
    if (id == null) {
        if (other.id != null)
            return false;
    } else if (!id.equals(other.id))
        return false;
    return true;
}


}

теперь два объекта A и B расширяют BaseEntity, как показано ниже

A extends BaseEntity{
    `B b`
     B getB(){return b;)
     void setB(B b){this.b=b;}
}

B extends BaseEntity{
}

object b1;
object a1;
a1.set(b1);
session.save(a1) //cascade save;

закрыть сеанс загрузить a с ленивым b и попробовать a1.getB(). equals (b1) дает false но если я сравню с a1.getB(). getId(). equals (b1.getId()), то дает истинное странное!! я думаю, что это из-за объекта-посредника java assist, так или иначе, чтобы разрешить это?

Ответ 1

Чтобы иметь возможность lazy-load ассоциации a.b, Hibernate устанавливает поле b в a для прокси. Прокси - это экземпляр класса, который расширяет B, но не является B. Таким образом, ваш метод equals() всегда будет терпеть неудачу при сравнении экземпляра non-proxy B с экземпляром proxy B, поскольку он сравнивает классы обоих объектов:

if (getClass() != obj.getClass())
    return false;

В случае объектов Hibernate вы должны заменить это на

if (!(obj instanceof B)) {
    return false;
}

Также обратите внимание, что

  • Hibernate рекомендует не применять equals() и hashCode() с помощью идентификатора, а с помощью естественного идентификатора. Реализация его с помощью идентификаторов может вызвать проблемы, поскольку объекты не имеют идентификатора до тех пор, пока они не будут сохранены и не будет создан идентификатор.
  • При использовании наследования объектов проблема еще хуже. Предположим, что B является суперклассом двух субинтетов B1 и B2. Hiberante не может знать, какой тип (B1 или B2) a.b перед загрузкой. Таким образом, a.b будет инициализирован прокси-серверу, который является подклассом B, но не является подклассом B1 или B2. Поэтому методы hashCode() и equals() должны быть реализованы в B, но не должны быть переопределены в B1 и B2. Два экземпляра B должны считаться равными, если они являются экземплярами B и имеют одинаковый идентификатор.

Ответ 2

Я использую Hibernate.getClass в течение многих лет, и я никогда не замечал проблемы:

@Override    
public boolean equals(final Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (Hibernate.getClass(this) != Hibernate.getClass(obj)) {
        return false;
    }

    ... check for values

    return true;
}

Ответ 3

Вы также можете заставить его работать таким образом, полезно, если вы не знаете, какой экземпляр B (может случиться, если ваш equals находится в суперклассе)

if (HibernateProxyHelper.getClassWithoutInitializingProxy(this) != HibernateProxyHelper.getClassWithoutInitializingProxy(obj)) 
    return false

Ответ 4

Это в основном эффект стандартного наследования Java.

a1.getB().equals(b1) использует Object.equals() (за исключением того, что вы переопределили equals() в своем классе), который возвращает только true, если a1.getB() и b1 - это один и тот же экземпляр. Я не знаю, что вы сделали точно (форматирование кода нарушено), но похоже, что вы снова загрузили a в другой сеанс, так что вы получите новый экземпляр для a и a.getB(), и, следовательно, Object.equals() возвращает false.

a1.getB().getId().equals(b1.getId()) использует Long.equals(), который возвращает true, если длинные значения одинаковы (даже для разных экземпляров объекта Long), и эти значения, очевидно, одинаковы.