Остановите Hibernate от обновления коллекций, когда они не изменились - PullRequest
10 голосов
/ 18 января 2011

У меня есть две сущности, определенные следующим образом (несвязанные вещи удалены):

@Entity
@Table(...)
public class MasterItem implements java.io.Serializable {

  private Set<CriticalItems> criticalItemses = new HashSet<CriticalItems>(0);

  @OneToMany(fetch = FetchType.EAGER, mappedBy = "masterItem", orphanRemoval = true,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
    public Set<CriticalItems> getCriticalItemses() {
        return this.criticalItemses;
    }
}

CriticalItems определяется следующим образом:

@Entity
@Table(...)
public class CriticalItems implements java.io.Serializable {

    private MasterItem masterItem;

    @ManyToOne(fetch = FetchType.LAZY, optional = false,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE})
    @JoinColumn(name = "mi_item_id", nullable = false)
    public MasterItem getMasterItem() {
        return this.masterItem;
    }
}

И в моем коде DAO - у меня есть следующие методы:

public MasterItem load(int id) {
    MasterItem results = (MasterItem) getSessionFactory().getCurrentSession()
        .get("com.xxx.MasterItem", id);

}

public void save(MasterItem master) {
    // master has been changed by the UI since it
    getSessionFactory().getCurrentSession().saveOrUpdate(master);
}

Когда я загружаю MasterItem, он загружается правильно, а также загружает набор CriticalItems с данными в соответствии с указаниями. Затем я отправляю эти данные в свой пользовательский интерфейс и получаю обновленную копию обратно, которую затем пытаюсь сохранить. Пользователь обновляет поля в объекте MasterItem, но не касается набора CriticalItems или чего-либо в нем - он остается неизменным.

Когда вызывается мой метод save (), Hibernate настаивает на отправке обновлений SQL для каждого элемента в наборе CriticalItems, даже если ни один из них не изменился никоим образом.

После некоторого копания, вот что, я думаю, происходит. Когда я делаю saveOrUpdate (), Hibernate видит, что мой объект MasterItem находится в отключенном состоянии, поэтому он пытается перезагрузить его с диска. Однако при этом он, похоже, использует подготовленный оператор (который был автоматически создан Hibernate при запуске), и этот подготовленный оператор не пытается присоединиться к данным CriticalItems.

Таким образом, Hibernate имеет мой обновленный объект MasterItem с полным набором CriticalItems, но использует MasterItem без коллекций в качестве своего объекта «previousState». Таким образом, все CriticalItems обновляются через SQL (не вставляются, что само по себе интересно).

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

Любое понимание будет оценено.

UPDATE: Основываясь на комментариях, я думаю, что понимаю разницу между saveOrUpdate () и merge (). Я понимаю, что saveOrUpdate () приведет к SQL INSERT или к SQL UPDATE во всех случаях, и это объединение, теоретически, будет выдавать обновления только в том случае, если объект изменился из своего постоянного состояния, но для определения этого, Hibernate сначала необходимо перезагрузить объект с помощью SQL SELECT.

Итак, я подумал, что могу просто вернуться в свой код и изменить saveOrUpdate () на merge (), и это сработает, но это не совсем так.

Когда я использовал merge (), я получал

org.springframework.orm.hibernate3.HibernateSystemException: could not initialize proxy - no Session; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session

, но все работало нормально, если я вернулся к saveOrUpdate ().

Я наконец-то узнал почему - я не включил CascadeType.MERGE в мою @Cascade аннотацию (тьфу). Как только я это исправил, исключение ушло.

Ответы [ 2 ]

16 голосов
/ 18 января 2011

Это семантическое различие между update() и merge().

С Сохранение Java Кристиана Бауэра и Гэвина Кинга с Hibernate (Я не могу найти четкое объяснение этого поведенияДокументы Hibernate):

Метод update () принудительно обновляет постоянное состояние объекта в базе данных, всегда планируя ОБНОВЛЕНИЕ SQL.
...
Это нене имеет значения, изменен ли объект элемента до или после его передачи в update ().
...
Hibernate всегда обрабатывает объект как грязный и планирует SQL UPDATE., который будет выполняться во время сброса.

С другой стороны, merge() сначала запрашивает базу данных и не выполняет обновление, если состояние не изменилось.

Итак, если вы хотите, чтобы Hibernate запрашивал базу данныхВо-первых, вам нужно использовать merge() (хотя поведение по умолчанию update() можно изменить, указав @org.hibernate.annotations.Entity(selectBeforeUpdate = true) для ваших сущностей).

1 голос
/ 18 января 2011

попробуйте добавить столбец ревизии (оптимистическая блокировка) к вашим сущностям

@Version
Date lastModified;
...