Hibernate javassist прокси и `Object#equals`

При предоставлении реализации #equals для UDT в Java одним из условий является то, что переданный объект аргумента должен быть экземпляром текущего класса, в противном случае мы быстро return false ошибемся, см. Эффективная Java (EJ2). Однако при использовании Hibernate 4 мы можем получить экземпляры прокси-сервера javassist из-за ленивой загрузки, когда это условие #equals не сработает. Что было бы лучшим выбором, чтобы преодолеть это? Несколько вариантов, о которых я могу думать, это:

  • расширить реализацию equals, чтобы учесть случай прокси. Минусы: плата за ремонтопригодность, аппаратная зависимость от прокси-инфраструктуры Hibernate, хакерские модели, модели сущностей или доменов не должны зависеть от используемого ORM, т. Е. Поскольку они могут повторно использоваться в разных контекстах, где нет необходимости в ORM, например. Качающийся пользовательский интерфейс.
  • проверьте, является ли это прокси перед вызовом equals. Минусы: не всегда возможно, т. е. иметь дело с коллекциями и неявными вызовами equals, например, Map.
  • Воздержитесь от использования ленивой загрузки. Минусы: не разумно и неэффективно во всех случаях использования.

ОБНОВИТЬ

Снова просматривая EJ2, я считаю, что следующее будет работать для всех сценариев (тип-тип, тип-прокси, прокси-тип и прокси-прокси), но, как указано в одном из комментариев ниже, он может зацикливаться навсегда, если тип сравнивается с совершенно другой тип, например Person.equals(Employee), и оба используют одни и те же критерии равенства EJ2.

    if (this.getClass() != anObject.getClass())
    {
        return anObject.equals(this);
    }

person SkyWalker    schedule 24.12.2012    source источник
comment
просто убедитесь, что прокси расширит класс вашей сущности?   -  person Aviram Segal    schedule 24.12.2012
comment
экземпляры прокси не расширяются, см. InvocationHandler, а условие (this.getClass() == anObject.getClass()), конечно же, будет оцениваться как ложное.   -  person SkyWalker    schedule 24.12.2012
comment
Вы упустили вариант: проигнорируйте совет по эффективной Java и разрешите подклассы в .equals().   -  person Ryan Stewart    schedule 24.12.2012
comment
Я имею в виду, что прокси может быть назначен типу объекта, например объект A, затем A a = proxy;   -  person Aviram Segal    schedule 24.12.2012
comment
@GiovanniAzua Вы уверены, что не путаете прокси-технологии? InvocationHandler — это то, что JDK использует для прокси-интерфейсов. Когда Hibernate проксирует ваши классы, я на 99% уверен, что он генерирует подкласс вашего класса сущности. (Это происходит при использовании CGLIB, и я не могу представить, как это могло бы работать иначе с Javassist, учитывая, что код, который упоминает Aviram, должен работать.)   -  person millimoose    schedule 24.12.2012
comment
Код в и, если вы поместите его в 2 разных класса и вызовете равные, разве он не будет работать вечно?   -  person Aviram Segal    schedule 24.12.2012
comment
true :( работает только для того же типа или его прокси.   -  person SkyWalker    schedule 24.12.2012
comment
Мой совет: ослабьте условие EJ2 до if (other instanceof ThisClass). На самом деле, если вы сделаете equals() метод final, я считаю, что метод будет формально правильным. (Конечно, это предотвратит изменение семантики равенства подклассами - от этого зависит формальная правильность.)   -  person millimoose    schedule 24.12.2012
comment
См. также трактовку этого вопроса Анжеликой Лангер:   -  person millimoose    schedule 24.12.2012


Ответы (4)


Я наткнулся на ту же проблему. Я исправил это, изменив метод .equals.

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (!getClass().isAssignableFrom(obj.getClass()))
        return false;
    AbstractEntity other = (AbstractEntity) obj;
    if (getId() == null) {
        if (other.getId() != null)
            return false;
    } else if (!getId().equals(other.getId()))
        return false;
    return true;

Хитрость заключается в том, чтобы не сравнивать классы, чтобы они были одинаковыми, а использовать isAssignableFrom-метод. Другой трюк заключается в том, чтобы не использовать прямые свойства (other.id), а использовать метод get (other.getId())

person Willem de Wit    schedule 15.08.2013
comment
getClass().isAssignableFrom(obj.getClass()) всегда возвращает мне false - person usr-local-ΕΨΗΕΛΩΝ; 29.10.2013
comment
Это помогло мне. Для тех, кому интересно: вот как это проверить: Employee employeeRef = em.getReference(Employee.class, id); Сотрудник = em.find(Employee.class, id); Assert.assertTrue(employee.equals(employeeRef)); - person Alan B. Dee; 05.09.2014

У меня нет репутации, чтобы комментировать ответ Виллема де Вита. Чем мне нужно опубликовать новый ответ.

Чтобы решить проблему с джешелоном, следует заменить эту строку:

if (!getClass().isAssignableFrom(obj.getClass()))

для

if ( !obj.getClass().isAssignableFrom(getClass()) && !getClass().isAssignableFrom(obj.getClass()) )

Тогда вы убедитесь, что равные будут работать для всех сценариев (Тип-Тип, Тип-Прокси, Прокси-Тип и Прокси-Прокси).

У меня нет репутации, чтобы голосовать за ваш ответ. Я так несчастен!

person DHansen    schedule 11.03.2014

Вы можете сделать две вещи: 1. Изменить равенство, чтобы использовать instanceof вместо равенства классов. Тип прокси не равен типу сущности, а расширяет тип сущности.

  1. Разверните прокси, чтобы получить сам объект (есть несколько инструментов гибернации, которые помогут вам сделать это)
person Doron Manor    schedule 24.03.2013

Ответ DHansen выше близок, но для меня (с использованием Hibernate) это решило проблему:

if (!Hibernate.getClass(this).equals(Hibernate.getClass(obj))) { return false; } , как предложил Dr. Ханс-Петер Шторр

Также важно всегда использовать "геттеры", как предложил Виллем де Вит выше.

person Lars    schedule 27.04.2017