Реализация equals () и hashCode (), когда нет естественного ключа? - PullRequest
3 голосов
/ 09 июля 2011

Этот вопрос является продолжением вопросов:

Должен ли я писать методы equals () в сущностях JPA? и Какова лучшая практика при реализации equals () для сущностей с сгенерированными идентификаторами

Сначала фон ...

Вы можете регулярно встречать следующие первичные ключевые созвездия:

  1. Естественные ключи (бизнес-ключи): обычно набор реальных, многостолбцовых атрибутов сущности
  2. Искусственные ключи (суррогатные ключи): бессмысленные, обычно с автоинкрементом (IDENTITY, AUTO_INCREMENT, AUTOINCREMENT, SEQUENCE, SERIAL, ...) ID
  3. Гибридные ключи (полунатуральные / полусинтетические ключи): обычно состоят из искусственного идентификатора и некоторых дополнительных естественных столбцов, например любой таблицы, которая ссылается на другую таблицу, которая использует идентификатор и расширяет этот ключ (entity_id, ordinal_nbr ) или аналогичные.

Частый сценарий: многозначные ссылки на корневую, ветвь или таблицу наследования листьев, которые имеют общий «глупый» идентификатор посредством идентификации отношения / зависимого ключа. Корневые (и ветвящиеся) таблицы часто имеют смысл, когда другая таблица должна ссылаться на все типы сущностей, например PostAddresses -> Контакты, где у контактов есть подстолы Персоны, Клубы, и объекты, которые не имеют ничего общего, кроме того, что являются "контактными".

Теперь в JPA:

В Java мы можем создавать новые объекты сущностей, чей PK может быть неполным (нулевым или частично нулевым), сущность (строка), которую СУБД в конечном итоге не позволит нам вставить в БД.

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

Вопрос:

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

Там явно не так много выбора ...

Одной (наивной) целью было бы достижение той же «переходной сопоставимости». Это можно сделать? Если нет, то как выглядит общий подход для реализаций искусственного идентификатора equals () и hashCode ()?


Примечание: Я уже использую Apache EqualsBuilder и HashCodeBuilder ... Я намеренно "наивизировал" мой вопрос.

Ответы [ 3 ]

3 голосов
/ 09 июля 2011

Я думаю, что предмет проще, чем указывают дискуссии.

Возьмите идентификаторы базы данных, если они есть, в противном случае используйте Object # equals / object identity

Почему? Если вы помещаете новый объект в базу данных, JPA ничего не делает, кроме как сопоставление нового сгенерированного идентификатора из базы данных с идентификатором объектов. С другой стороны, это означает, что идентификация объекта также является первичным ключом.

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

После сохранения сущности идентификатор базы данных выполняет эту работу полностью, поскольку теперь у вас могут быть клоны одной и той же сущности, которые имеют только один и тот же идентификатор базы данных. (Но теперь может иметь несколько областей памяти / идентификаторов объектов)

1 голос
/ 09 июля 2011

Одним из часто предлагаемых методов является использование идентификаторов UUID для идентификаторов, которые имеют несколько недостатков.

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

Преимущество UUID состоит в том, что вам не нужно реализовывать отдельный метод hashCode () equals () для каждой сущности.

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

@Configurable
@MappedSuperclass
@EntityListeners({ModelListener.class})
@SuppressWarnings("serial")
public abstract class ModelBase implements Serializable {

     //~~ Instance Fields =====================================

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "id", nullable = false, updatable=false, unique=true)
     protected Long id;

    @Column(name="__UUID__", unique=true, nullable=false, updatable=false, length = 36)
    private String uuid = java.util.UUID.randomUUID().toString();

    //~ Business Methods =====================================

    @Override
    public String toString() {
        return new ToStringCreator(this)
            .append("id", getId())
            .append("uuid", uuid())
            .append("version", getVersion())
             .toString(); 
    }

    @Override
    public int hashCode() {
        return uuid().hashCode();
    }

    @Override
    public boolean equals(Object o) {
        return (o == this || (o instanceof ModelBase && uuid().equals(((ModelBase)o).uuid())));
     }

    /**
     * Returns this objects UUID.
     * 
     * @return - This object's UUID.
     */
    public String uuid() {
        return uuid;
    }

    //~ Accessor Methods ======================================

    public Long getId() {
        return id;
    }

    @SuppressWarnings("unused")
    private void setId(Long id) {
        this.id = id;
    }

     @SuppressWarnings("unused")
    private String getUuid() {
        return uuid;
    }

    @SuppressWarnings("unused")
    private void setUuid(String uuid) {
        this.uuid = uuid;
     }
}

Просто расширьте ModelBase для всех ваших сущностей. Преимущество этого метода в том, что uuid назначается, как только объект создан. Но у нас все еще есть назначенный идентификатор, который мы можем использовать в нашем коде приложения для запроса определенных объектов. По сути, поле uuid никогда не используется и даже не рассматривается в нашем коде приложения, кроме как для сравнения. Работает как шарм.

1 голос
/ 09 июля 2011

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

...