Подтвердить что ты не робот

Почему Hibernate пытается удалить, когда я пытаюсь обновить/вставить?

В моем приложении у меня есть типы Hibernate с отображением (общий случай):

class RoleRule {
  private Role role;
  private PermissionAwareEntity entity; // hibernate-mapped entity for which permission is granted
  private PermissionType permissionType; // enum

  @ManyToOne
  @JoinColumn(name = "ROLE_ID")
  public Role getRole() {
    return role;
  }
  public void setRole(Role role) {
    this.role = role;
  }

}

class Role {
  private Set<RoleRule> rules = new HashSet<RoleRule>(0);

  @OneToMany(cascade=CascadeType.ALL)
  @JoinColumn(name="ROLE_ID")
  public Set<RoleRule> getRules() {
    return rules;
  }
  public void setRules(Set<RoleRule> rules) {
    this.rules = rules;
  }

}

Все классы имеют переопределение equals() & hashCode().

Мое приложение позволяет настраивать роли (только с помощью системных администраторов, не волнуйтесь), и среди других полей позволяет создавать новые правила ролей. Когда создается новое правило, я пытаюсь создать новый объект RoleRule и вставить его в поле ролей rules. Я вызываю session.update(role) для применения изменений в базе данных.

Теперь появляется уродливая часть... Hibernate решает сделать следующее, закрывая транзакцию и промывку:

  • Вставьте новое правило в базу данных. Отлично.
  • Обновите другие поля ролей (а не коллекции). Пока все хорошо.
  • Обновите существующие правила, даже если в них ничего не изменилось. Я могу жить с этим.
  • Обновите существующие правила еще раз. Здесь вставка из журнала, включая автоматический комментарий:
/* delete one-to-many row Role.rules */
update ROLE_RULE set ROLE_ID=null where ROLE_ID=? and ROLE_RULE_ID=?

Конечно, все поля не являются нулевыми, и эта операция не срабатывает эффектно.

Может кто-нибудь попытаться объяснить, почему Hibernate сделает это??? И что еще более важно, как я могу обойти это frak?

EDIT. Я был так уверен, что это как-то связано с отображением, а затем мой босс по прихоти удалил equals() и hashCode() в обоих классах, воссоздал их, используя Затмение, и таинственно это решило проблему.

Мне все еще очень любопытно узнать о моем вопросе. Может ли кто-нибудь предположить, почему Hibernate сделает это?

4b9b3361

Ответ 1

Обычно я использовал два метода обновления коллекции (много сторон одного-ко-многим) в Hibernate. Метод грубой силы заключается в очистке коллекции, вызове сохранения родителя, а затем вызове flush. Затем добавьте все элементы коллекции обратно и снова вызовите save на родительском элементе. Это удалит все, а затем вставляет все. Флеш посередине является ключевым, потому что он заставляет удаление выполняться перед вставкой. Вероятно, лучше всего использовать этот метод только для небольших коллекций, поскольку он повторно вставляет все из них.

Второй способ сложнее закодировать, но более эффективен. Вы перебираете новый набор дочерних элементов и вручную изменяете те, которые все еще там, удаляете те, которые не являются, а затем добавляете новые. В псевдокоде, который будет:

copy the list of existing records to a list_to_delete
for each record from the form
   remove it from the list_to_delete
   if the record exists (based on equals()?  key?)
     change each field that the user can enter
   else if the record doesn't exist
     add it to the collection
end for
for each list_to_delete
  remove it
end for
save

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

Ответ 2

См. ответ на вопрос "Переопределение значений equals и hashCode в Java.

В нем объясняется, как переопределить методы equals и hashCode, которые, казалось, были вашей проблемой, поскольку они работали после их перезаписи.

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

Ответ 3

Я не уверен, что это решение, но вы можете попробовать:

@OneToMany(mappedBy = "role")

И не имеет аннотации @JoinColumn? Я думаю, что оба объекта пытаются "связать" связь, поэтому SQL может быть испорчен?

Кроме того, если вы хотите обновить только затронутые столбцы, вы можете использовать аннотацию, специфичную для спящего режима, для класса:

@Entity
@org.hibernate.annotations.Entity(
    dynamicInsert = true, dynamicUpdate = true
)

Ответ 4

Ответ брайана Детерлинга помог мне преодолеть фантомное удаление. Я бы хотел, чтобы он поставил настоящий код. Вот что я получил из его предложения 1. Публикация сообщений для того, чтобы кто-то использовал их или комментировал мой код.

// snFile and task share many to many relationship

@PersistenceContext
private EntityManager em;

public SnFile merge(SnFile snFile) {
        log.debug("Request to merge SnFile : {}", snFile);

        Set<Task> tasks = taskService.findBySnFilesId(snFile.getId());
        if(snFile.getTasks() != null) {
            snFile.getTasks().clear();
        }
        em.merge(snFile);
        em.flush();
        if(tasks != null) {
            if(snFile.getTasks() != null)
                snFile.getTasks().addAll(tasks);
            else
                snFile.setTasks(tasks);
        }

        return em.merge(snFile);
    }