Реализация equals / hashCode с использованием объекта ManyToOne, на который ссылается ссылка

#java #hibernate #jpa #identity

#java #переход в спящий режим #jpa #идентификация

Вопрос:

Предположим, у нас есть следующие объекты JPA:

 class Parent {
    @Id
    private Long id;
}

class Child {
    @Id
    private Long id;

    @Column
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Parent parent;
}
  

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

Теперь я не уверен, каким наилучшим подходом было бы реализовать equals (и hashCode) для дочернего класса.

Ссылается на идентификатор приложения

Поскольку прокси-сервер приложения будет загружен, а его идентификатор будет установлен на прокси-сервере, поэтому сам объект приложения не будет инициализирован:

 public boolean equals (Object o) {
    //null check, instanceof check ...
    return new EqualsBuilder().append(getName(), other.getName())
            .append(getParent().getId(), other.getParent().getId())
            .isEquals(); 
  

}

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

Не используйте объект, на который ссылается ссылка, для оценки равенства

Хорошо, но тогда мне нужно что-то еще. Я не хочу использовать идентификатор дочернего элемента (плохая практика), что оставляет мне только 1 вариант: использовать для этого отдельное свойство, например UUID. Я ничего не имею против использования UUID, но, конечно, только в том случае, если нет другого доступного варианта.

Мои вопросы:

  1. Я пропустил какой-то вариант?
  2. Какой, по вашему мнению, рекомендуемый способ сделать это?

Комментарии:

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

2. Почему эта реализация требует аннотаций к получателям, а не к полям?

3. @JB Nizet amp; Tom: если аннотация помещена в поле (id), то getId() приведет к инициализации прокси-сервера. Существует ошибка гибернации , связанная с этим. Смотрите также это объяснение .

4. прокси-сервера нет (или он всегда инициализирован), поскольку ассоциация запрашивается с нетерпением.

5. @JB, извините, ошибка должна быть ленивой, я ее исправил

Ответ №1:

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

 @Column(name="parent_id", insertable=false, updatable=false)
private String parentId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="parent_id")
private Parent parent;
  

Комментарии:

1. Интересно, не думал, что это будет возможно. Вы когда-нибудь делали это раньше?

2. TheStijn: Да, это работает, по крайней мере, с toplink essentials и hibernate.

3. Интересно. Если бы это был чистый SQL, я бы сделал первичный ключ дочерней таблицы составным естественным ключом, включающим идентификатор родителя и имя. Я не думал, что есть какой-либо способ сделать это в JDBC, но это позволило бы именно это — сопоставить parent_id с родительским полем, а также со строковым полем в составном ключе. Возможно.

4. Потребовалось некоторое время, прежде чем я дошел до этого, но это работает так, как было обещано. Tx.

5. Интересное решение, но для непостоянных родительских элементов мы сталкиваемся с той же проблемой, что и при использовании идентификатора в качестве бизнес-ключа: при создании нового Parent(); и присвоении ему SetParent(p); ParentID не задан, и методы equals() и hashCode() не работают.

Ответ №2:

Если вы можете получить доступ к EntityManagerFactory (и если вы можете получить доступ к EntityManager, EntityManager.getEntityManagerFactory() — это один из способов сделать это), другой способ получить идентификатор родителя — с помощью:

 emf.getPersistenceUnitUtil().getIdentifier(parent);
  

Тем не менее, вам все равно понадобится проверка null.

Комментарии:

1. Действительно ли это позволяет избежать извлечения объекта, на который ссылается ссылка, из базы данных?

2. Я предполагаю, что версия для гибернации, описанная выше, будет Serializable id = ((HibernateProxy) entity).getHibernateLazyInitializer().getIdentifier() . Но мне не нравится этот подход, он кажется неправильным. Кроме того, объект здесь может даже не быть прокси, что является еще одной проверкой, которую вам нужно выполнить.

3. Я думаю, что в JPA не имеет значения, прокси это или нет. На самом деле я не знаю, не загрузит ли это объект, но я предполагаю, что это не так. Я согласен, что это скорее взлом, чем решение.