Как сохранить класс, который зависит от внутреннего списка, используя Hibernate? - PullRequest
3 голосов
/ 01 декабря 2011

У меня есть класс History, в котором хранится история State объектов. Для этой цели History поддерживает внутренний список, точнее, ArrayList:

// states is 'life cycle object' -> cascading
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private final List<State> states;

History() {
    states = new ArrayList<State>();
}

Метод equals опирается на поле states:

public final boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (!(obj instanceof History)) {
        return false;
    }
    History other = (History) obj;
    // we do not use fields to ensure compatibility with lazy loading
    if (!Objects.equal(getStates(), other.getStates())) {
        return false;
    }
    return true;
}

В модульных тестах моей модели метод equals ведет себя как ожидалось; проверено equalsverifier .

Однако, выше уровня персистентности, метод equals ведет себя по-другому. Хотя два экземпляра History имеют одинаковый список состояний, они не равны.

Это связано с классом Hibernate PersistentBag, который - намеренно - нарушает API коллекции:

/**
 * Bag does not respect the collection API and do an
 * JVM instance comparison to do the equals.
 * The semantic is broken not to have to initialize a
 * collection for a simple equals() operation.
 * @see java.lang.Object#equals(java.lang.Object)
 */
public boolean equals(Object obj) {
    return super.equals(obj);
}

Ну, это делает мой equals метод бесполезным ...

Как я мог сохранить класс History без потери семантики equals?

P.S .: Существует соответствующий открытый выпуск на https://hibernate.onjira.com/browse/HHH-5409.

Обновление

Это мой тестовый пример:

  1. Я создаю историю h и добавляю несколько состояний.
  2. В сеансе s1 я сохраняю h в базе данных, используя Hibernate.
  3. В сеансе s2 я извлекаю h из базы данных, снова используя какой-то поисковый запрос через Hibernate. Давайте назовем этот извлеченный экземпляр h*.
  4. Теперь я звоню h.equals(h*). Я ожидаю, что равенство имеет место. Однако h.equals(h*) возвращает false. Примечание: конечно, h != h* имеет место.

Это связано с тем, что Hibernates использует экземпляр PersistentBag вместо ArrayList для представления состояний h*.

На первый взгляд трудно найти другой бизнес-ключ для History. Две истории равны, если они имеют одинаковый ход событий, не так ли? Кроме того, почему я должен перенести мою модель на уровень персистентности, в то время как ее логика кажется здравой (конечно, я могу ошибаться).

Ответы [ 2 ]

0 голосов
/ 23 сентября 2016

Почему вы хотите включить List в сравнение equals / hashCode для корневого объекта? Это неправильный подход.

Равенство должно основываться на базовом ограничении единственности записи таблицы. В идеале, , вы должны иметь естественный ключ в каждой таблице базы данных , такой как SSN, адрес электронной почты, имя домена, UUID .

Иногда у вас нет естественного идентификатора, но тогда у вас все еще есть первичный ключ. Вопреки распространенному мнению, вы можете использовать PK для equals / hashCode , вам просто нужно убедиться, что hashCode отображает постоянное значение для каждого перехода состояния сущности.

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

0 голосов
/ 01 декабря 2011

Полагаю, вы не (правильно) реализовали метод equals для State? Если это так, находятся ли государства в списке в одинаковом порядке?

Но вы не должны использовать ссылочные объекты для определения вашего equals / hashCode, особенно если, как и в вашем случае, они загружаются лениво. Если вы загружаете свою историю без состояний, а затем по какой-либо из многочисленных причин, равный становится hibernate, выполнит 2 запроса для этого. Кроме того, если вы отключите History, а затем вызовете equals, вы получите LazyIntializationException.

Попробуйте найти другой бизнес-ключ для своей истории, который не зависит и от других сущностей.

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