Остановить Hibernate от обновления коллекций, если они не изменились

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

@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 для каждого элемента в Set CriticalItems, хотя ни один из них не изменился каким-либо образом.

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

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

Я сделал что-то в своих аннотациях, которые вызвали это поведение? Я знаю, что я могу использовать Interceptor, чтобы узнать, что элемент действительно изменился, или изменил флаг грязных, чтобы переопределить алгоритм по умолчанию 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 (ugh). Как только я исправил это, исключение исчезло.

Ответ 1

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

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

Метод update() принудительно обновляет постоянное состояние объекта в база данных, всегда планирующая SQL UPDATE.
...
Не имеет значения, изменен ли объект объекта до или после его передачи обновление().
...
зимовать всегда обрабатывает объект как грязный и планирует SQL UPDATE., который будет выполнен во время флеша.

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

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

Ответ 2

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

@Version
Date lastModified;