Равенство объектов в контексте hibernate / webapp - PullRequest
12 голосов
/ 27 апреля 2010

Как вы справляетесь с равенством объектов для объектов Java, управляемых Hibernate? В книге «Спящий режим в действии» говорится, что следует отдавать предпочтение бизнес-ключам вместо суррогатных ключей.
Большую часть времени у меня нет бизнес-ключа. Подумайте об адресах, привязанных к человеку. Адреса хранятся в наборе и отображаются в Wicket RefreshingView (со стратегией ReuseIfEquals).

Я мог бы использовать суррогатный идентификатор или использовать все поля в функциях equals () и hashCode ().
Проблема в том, что эти поля меняются в течение жизни объекта. Либо потому, что пользователь ввел некоторые данные, либо идентификатор изменяется из-за того, что JPA merge () вызывается внутри фильтра OSIV (Open Session in View).

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

Что я пробовал до сих пор:

  • equals () основано на hashCode (), который использует идентификатор базы данных (или super.hashCode (), если id равен нулю). Проблема: новые адреса начинаются с нулевого идентификатора, но получают идентификатор при присоединении к человеку, и этот человек сливается () (повторно присоединяется) в фильтр osiv.
  • Ленивый вычислить хеш-код при первом вызове hashCode () и сделать этот хеш-код @Transitional. Не работает, так как merge () возвращает новый объект, а хэш-код не копируется.

Мне нужен идентификатор, который присваивается при создании объекта, я думаю. Какие будут мои варианты здесь? Я не хочу вводить дополнительное постоянное свойство. Есть ли способ явно сказать JPA назначить идентификатор для объекта?

Привет

Ответы [ 5 ]

14 голосов
/ 27 апреля 2010

Использование id объекта не является хорошей идеей, потому что временные объекты еще не имеют идентификатора (и вы все еще хотите, чтобы временный объект был потенциально равен постоянному).

Использование всех свойств (кроме идентификатора базы данных) также не очень хорошая идея, поскольку все свойства просто не являются частью идентичности.

Таким образом, предпочтительный (и правильный) способ реализации равенства - это использование бизнес-ключа , как описано в Java Persistence с Hibernate :

Реализация равенства с помощью бизнес-ключа

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

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

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

Для класса User username является отличным кандидатом в бизнес-ключ. Это никогда не ноль, он уникален с ограничением базы данных и редко меняется, если вообще:

    public class User {
        ...
        public boolean equals(Object other) {
            if (this==other) return true;
            if ( !(other instanceof User) ) return false;
            final User that = (User) other;
            return this.username.equals( that.getUsername() );
        }
        public int hashCode() {
            return username.hashCode();
        }
}

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

На всякий случай, Equals And HashCode - еще одно интересное чтение.

0 голосов
/ 29 апреля 2010

Спасибо за ваш вклад. Я решил использовать суррогатные ключи и предоставить их прямо во время создания объекта. Таким образом, я остаюсь в стороне от всех этих «редко» меняющихся вещей и имею что-то твердое, на чем можно основывать личность. Первые тесты выглядят неплохо.

спасибо всем за ваше время. К сожалению, я могу принять только один ответ в качестве решения, я приму Паскаля, так как он дал мне хорошее чтение;)

наслаждаться

0 голосов
/ 27 апреля 2010

Вопрос в том, как часто у вас может быть несколько несохраненных объектов, которые могут быть дубликатами, которые должны входить в набор или карту? Для меня ответ практически никогда, поэтому я использую суррогатные ключи и super.equals / hashcode для несохраненных объектов.

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

0 голосов
/ 27 апреля 2010

Я использую, чтобы сделать это таким образом: равные и хэш-код используют ключ, когда он был установлен, в противном случае равные используют базовую реализацию (aka ==). Это также должно работать, если hashcode() возвращает super.hashcode() вместо 0.

@Override
public int hashCode() {
    if (code == null) {
        return 0;
    } else {
        return code.hashCode();
    }
}

@Override
public boolean equals(Object obj) {
    if (obj instanceof PersistentObject && Hibernate.getClass(obj).equals(Hibernate.getClass(this))) {
        PersistentObject po = (PersistentObject) obj;

        if (code == null) {
            return po.code == null && this == po;
        } else {
            return code.equals(po.getCode());
        }
    } else {
        return super.equals(obj);
    }
}
0 голосов
/ 27 апреля 2010

Может быть, свойство transient сделает это? Таким образом, вам не нужно беспокоиться о постоянстве. Как это:

@Transient
private Integer otherId;
...