«1. Обзор

В этом руководстве мы рассмотрим обработку равенства с объектами JPA Entity.

2. Соображения

В общем, равенство просто означает, что два объекта одинаковы. Однако в Java мы можем изменить определение равенства, переопределив методы Object.equals() и Object.hashCode(). В конечном счете, Java позволяет нам определить, что значит быть равным. Но сначала нам нужно рассмотреть несколько вещей.

2.1. Коллекции

Коллекции Java группируют объекты вместе. Логика группировки использует специальное значение, известное как хэш-код, для определения группы объекта.

Если значение, возвращаемое методом hashCode(), одинаково для всех сущностей, это может привести к нежелательному поведению. Допустим, наш объект сущности имеет первичный ключ, определенный как id, но мы определяем наш метод hashCode() как:

@Override
public int hashCode() {
    return 12345;
}

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

@Override
public int hashCode() {
    return id * 12345;
}

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

2.2. Переходные объекты

Вновь созданные объекты объектов JPA, которые не связаны с контекстом постоянства, считаются находящимися в переходном состоянии. Эти объекты обычно не имеют заполненных членов @Id. Следовательно, если equals() или hashCode() используют идентификатор в своих вычислениях, это означает, что все временные объекты будут равны, потому что все их идентификаторы будут нулевыми. Есть не так много случаев, когда это желательно.

2.3. Подклассы

Подклассы также являются проблемой при определении равенства. Обычно классы сравнивают в методе equals(). Поэтому включение метода getClass() поможет отфильтровать подклассы при сравнении объектов на предмет равенства.

Давайте определим метод equals(), который будет работать только в том случае, если объекты относятся к одному классу и имеют один и тот же идентификатор:

@Override
public boolean equals(Object o) {
    if (o == null || this.getClass() != o.getClass()) {
        return false;
    }
    return o.id.equals(this.id);
}

3. Определение равенства

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

3.1. Никаких переопределений

По умолчанию Java предоставляет методы equals() и hashCode() благодаря всем объектам, происходящим от класса Object. Поэтому самое простое, что мы можем сделать, это ничего не делать. К сожалению, это означает, что при сравнении объектов, чтобы они считались равными, они должны быть одними и теми же экземплярами, а не двумя отдельными экземплярами, представляющими один и тот же объект.

3.2. Использование ключа базы данных

В большинстве случаев мы имеем дело с объектами JPA, которые хранятся в базе данных. Обычно эти объекты имеют первичный ключ, который является уникальным значением. Следовательно, все экземпляры этой сущности, имеющие одинаковое значение первичного ключа, равны. Итак, мы можем переопределить equals(), как мы делали выше для подклассов, а также переопределить hashCode(), используя только первичный ключ в обоих случаях.

3.3. Использование бизнес-ключа

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

Допустим, мы знаем, что адрес электронной почты всегда будет уникальным, даже если он не является полем @Id. Мы можем включить поле электронной почты в методы hashCode() и equals():

public class EqualByBusinessKey {

    private String email;

    @Override
    public int hashCode() {
        return java.util.Objects.hashCode(email);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (obj instanceof EqualByBusinessKey) {
            if (((EqualByBusinessKey) obj).getEmail().equals(getEmail())) {
                return true;
            }
        }

        return false;
    }
}

4. Заключение

В этом руководстве мы обсудили различные способы обработки равенства при написании объектов сущностей JPA. Мы также описали соображения, которые мы должны учитывать при выборе подхода. Как всегда, полный исходный код можно найти на GitHub.