Как правильно переопределить методы hashCode () и equals () для персистентной сущности? - PullRequest
12 голосов
/ 18 декабря 2009

У меня простая роль класса:

@Entity
@Table (name = "ROLE")
public class Role implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;
    @Column
    private String roleName;

    public Role () { }

    public Role (String roleName) {
        this.roleName = roleName;
    }

    public void setId (Integer id) {
        this.id = id;
    }

    public Integer getId () {
        return id;
    }

    public void setRoleName (String roleName) {
        this.roleName = roleName;
    }

    public String getRoleName () {
        return roleName;
    }
}

Теперь я хочу переопределить его методы equals и hashCode. Мое первое предложение:

public boolean equals (Object obj) {
    if (obj instanceof Role) {
        return ((Role)obj).getRoleName ().equals (roleName);
    }
    return false;
}

public int hashCode () {
    return id; 
}

Но когда я создаю новый объект Role, его идентификатор равен нулю. Вот почему у меня есть некоторые проблемы с реализацией метода hashCode. Теперь я могу просто вернуть roleName.hashCode (), но что, если roleName не является обязательным полем? Я почти уверен, что не так сложно составить более сложный пример, который невозможно решить, возвращая hashCode одного из его полей.

Так что я хотел бы увидеть некоторые ссылки на связанные обсуждения или услышать ваш опыт решения этой проблемы. Спасибо!

Ответы [ 6 ]

13 голосов
/ 18 декабря 2009

Книга Бауэра и Кинга Сохранение Java в Hibernate не рекомендуется использовать ключевое поле для equals и hashCode. Они советуют вам выбрать поля полей бизнес-объекта (если бы не было искусственного ключа) и использовать их для проверки равенства. Таким образом, в этом случае, если имя роли не является обязательным полем, вы найдете нужные поля и используете их в комбинации. В случае кода, который вы публикуете, где rolename - это все, что у вас есть, кроме идентификатора, rolename - это то, с чем я бы пошел.

Вот цитата со страницы 398:

Мы утверждаем, что по существу каждый класс сущностей должен иметь некоторый бизнес-ключ, даже если он включает в себя все свойства класса (это подходит для некоторых неизменяемых классов). Бизнес-ключ - это то, что пользователь считает уникальной идентификацией конкретной записи, тогда как суррогатный ключ - это то, что используют приложение и база данных.

Равенство бизнес-ключа означает, что метод equals () сравнивает только те свойства, которые образуют бизнес-ключ. Это идеальное решение, которое позволяет избежать всех проблем, представленных ранее. Единственным недостатком является то, что это требует дополнительных усилий, чтобы определить правильный бизнес-ключ в первую очередь. Это усилие требуется в любом случае; важно идентифицировать любые уникальные ключи, если ваша база данных должна обеспечивать целостность данных посредством проверки ограничений.

Простой способ, который я использую для создания метода equals и hashcode, - это создание метода toString, который возвращает значения полей 'business key', а затем его использование в методах equals () и hashCode (). РАЗЪЯСНЕНИЕ: Это ленивый подход, когда меня не интересует производительность (например, во внутренних веб-приложениях rinky-dink), если ожидается, что производительность будет проблемой, тогда напишите методы самостоятельно или используйте средства генерации кода в вашей среде IDE.

7 голосов
/ 19 декабря 2009

Я сожалею, что прыгнул поздно с критикой, но никто больше не упомянул об этом, и здесь есть серьезный недостаток . Возможно два, на самом деле.

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

Контракт заключается в том, что объекты, для которых equals() возвращает true, должны возвращать равные значения хеш-кода , но в вашем классе выше поля id и roleName независимы.

Это крайне ошибочная практика: вы можете легко иметь два объекта с одинаковым значением roleName, но разными значениями id.

Практика состоит в том, чтобы использовать те же поля для генерации значения хеш-кода, которые используются методом equals (), и в том же порядке. Ниже моя замена для вашего метода хэш-кода:


public int hashCode () {
    return ((roleName==null) ? 0 : roleName.hashcode()); 
}

Примечание: я не знаю, что вы намеревались использовать в поле id в качестве хеш-кода, или что вы хотели сделать с полем id. Я вижу из аннотации, что он сгенерирован, но он извне сгенерирован, поэтому записанный класс не выполняет контракт.

Если по какой-то причине вы окажетесь в ситуации, когда этот класс исключительно управляется другим, который точно генерирует значения "id" для roleNames, которые выполняют контракт, у вас не будет проблем с функциональностью , но это все равно будет плохой практикой или, по крайней мере, иметь то, что люди называют «запахом кода». Помимо того факта, что в определении класса нет ничего, что могло бы гарантировать, что класс можно использовать только таким образом, хеш-коды не являются идентификаторами, поэтому идентификаторы не являются хеш-кодами .

Это не значит, что вы не можете использовать идентификатор с гарантированным равным для равных значений rolename-значений в качестве хеш-кода, но они концептуально не совпадают, поэтому, по крайней мере, , у вас должен быть блок комментария, чтобы объяснить ваш отход от ожидаемой практики.

И, как хорошее общее правило, если вам придется это делать, вы, вероятно, допустили ошибку проектирования. Не всегда, но возможно. Одна из причин этого? Люди не всегда читают комментарии, поэтому, даже если вы создадите отлично функционирующую систему, со временем кто-то «злоупотребит» вашим классом и вызовет проблемы.

Отсутствие этого позволяет самому классу управлять генерацией значений хеш-кода. И вы все равно можете сохранить и сделать доступным внешний сгенерированный идентификатор, для какой бы цели вы его не использовали.

2 голосов
/ 18 декабря 2009

Для бизнес-ключа объекта может потребоваться его родительское (или другое отношение «один к одному» или «многие к одному»). В этих случаях вызов equals () или hashcode () может привести к попаданию в базу данных. Помимо производительности, если сеанс закрыт, это вызовет ошибку. Я в основном отказался от попыток использовать бизнес-ключи; Я использую основной идентификатор и не использую несохраненные объекты в картах и ​​наборах. До сих пор работал хорошо, но это, вероятно, зависит от приложения (будьте осторожны при сохранении нескольких детей через родительский каскад). Иногда я использую отдельное бессмысленное ключевое поле, которое автоматически генерируется в конструкторе или создателе объекта.

1 голос
/ 22 мая 2012

Ниже вы найдете простые инструкции по созданию методов hashCode, equals и toString с использованием компоновщиков Apache.

* 1003 хэш-код * Если два объекта равны в соответствии с методом equals (), они должны иметь одинаковое значение hashCode () Возможно, что два разных объекта могут иметь одинаковый hashCode (). Пожалуйста, используйте уникальный Business ID для создания hashCode (это означает, что вы должны использовать какое-то уникальное свойство, которое представляет бизнес-сущность, например, имя) Hibernate Entity: пожалуйста, НЕ используйте Hibernate ID для создания хэш-кода Вы можете вызвать .appendSuper (super.hashCode ()), если ваш класс является подклассом @Override public int hashCode() { return new HashCodeBuilder() .append(getName()) .toHashCode(); } равно

  1. Пожалуйста, сравните Business ID (это означает, что вы должны использовать какое-то уникальное свойство, которое представляет бизнес-сущность, например, имя)
  2. Hibernate Entity: пожалуйста, НЕ сравнивайте Hibernate ID
  3. Hibernate Entity: используйте геттеры при доступе к полю другого объекта, чтобы разрешить Hibernate загрузить свойство
  4. Вы можете вызвать .appendSuper (super.equals (other)), если ваш класс подкласс

    @Override
    public boolean equals(final Object other) {
        if (this == other)
            return true;
        if (!(other instanceof TreeNode))
            return false;
        TreeNode castOther = (TreeNode) other;
        return new EqualsBuilder()
                .append(getName(), castOther.getName())
                .isEquals();
    }
    

ToString

  1. Пожалуйста, убедитесь, что toString не сгенерирует исключение NullPointerException.
  2. Вы можете вызвать .appendSuper (super.toString ()), если ваш класс является подклассом

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("Name", getName())
                .toString();
    }
    
1 голос
/ 18 декабря 2009

Как уже упоминалось, вы должны использовать бизнес-ключ для реализации равных и hashCode. Кроме того, вы должны сделать равенство и реализацию hashCode null-safe или добавить не нулевые ограничения (и инвариантные проверки в свой код), чтобы гарантировать, что бизнес-ключ никогда не будет нулевым.

Полагаю, добавление ограничений - правильный подход к вашей проблеме. В противном случае будут разрешены экземпляры ролей без имен, и все эти физические экземпляры будут считаться равными.

1 голос
/ 18 декабря 2009

Знать при переопределении hashCode и equals не простая задача, есть другое обсуждение, где у вас есть пример и ссылка на документацию здесь Какие проблемы следует учитывать при переопределении equals и hashCode в Java?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...