2011-01-18 17 views
11

Mam dwie fasoli podmiot określone następująco (niezwiązany materiał usunięto):Zatrzymaj Hibernate z aktualizacją kolekcji, gdy nie zostały zmienione

@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 jest zdefiniowany następująco:

@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; 
    } 
} 

i moim Kod DAO - Mam następujące metody:

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); 
} 

Po załadowaniu MasterItem, ładuje się poprawnie, d również ładuje zestaw CriticalItems z danymi, zgodnie z poleceniami. Następnie wysyłam te dane do mojego interfejsu użytkownika i otrzymuję zaktualizowaną kopię, którą następnie próbuję kontynuować. Użytkownik aktualizuje pola w obiekcie MasterItem, ale nie dotyka Zestawu Elementów Krytycznych ani niczego w nim zawartego - pozostaje niezmieniony.

Kiedy wywoływana jest moja metoda save(), Hibernate nalega na wysyłanie aktualizacji SQL dla każdego elementu w Zestawie Elementów Krytycznych, nawet jeśli żadna z nich w żaden sposób się nie zmieniła.

Po pewnym kopaniu, oto co myślę dzieje się. Kiedy robię saveOrUpdate(), Hibernate widzi mój obiekt MasterItem jest w stanie odłączonym, więc próbuje przeładować go z dysku. Jednak w takim przypadku wydaje się, że używa on przygotowanej instrukcji (która została automatycznie utworzona przez Hibernate przy uruchomieniu), a ta przygotowana instrukcja nie próbuje dołączyć do danych CriticalItems.

Tak Hibernacja ma mój zaktualizowany obiekt MasterItem z pełnym zestawem elementów CriticalItems, ale używa MasterItem bez kolekcji jako obiektu "previousState". W ten sposób wszystkie krytyczne itemy są aktualizowane za pomocą SQL (nie włożone, co jest interesujące samo w sobie).

Czy zrobiłem coś w moich adnotacjach, które spowodowały takie zachowanie? Wiem, że mogę użyć Interceptora, aby dowiedzieć się, czy element naprawdę się zmienił, lub zmienić brudną flagę, aby zastąpić domyślny algorytm Hibernate - ale wydaje się, że to coś, co Hibernate powinien sam poradzić sobie bez mojej interwencji.

Każdy wgląd byłby doceniony.

UPDATE: podstawie wypowiedzi, myślę, że zrozumieć różnicę między saveOrUpdate() i scalić(). Zdaję sobie sprawę, że saveOrUpdate() spowoduje albo SQL INSERT lub SQL UPDATE we wszystkich przypadkach, i że scalenie, w teorii, wyda tylko aktualizacje, jeśli obiekt zmienił się z trwałego stanu, ale w celu ustalenia tego, Hibernate musi najpierw przeładować obiekt przez SQL SELECT.

Tak, myślałem, że mógłbym tylko wrócić do mojego kodu i zmienić saveOrUpdate(), aby scalić() i to by działało, ale to nie było tak.

Kiedy użyłem seryjnej(), byłem coraz

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

ale to działało w porządku, gdybym zmienił z powrotem do saveOrUpdate().

W końcu dowiedziałem się, dlaczego - nie zawierałem CascadeType.MERGE w mojej adnotacji @Cascade (ugh). Kiedy to naprawiłem, wyjątek odszedł.

+0

czy masz optymistyczną kolumnę blokującą (kolumna z adnotacją @version z typem albo datą int) – lweller

Odpowiedz

16

To semantyczne różnica między update() i merge().

Od Christian Bauer and Gavin King's Java Persistence with Hibernate (nie mogę znaleźć jasne wyjaśnienie tego zachowania w docs hibernacji):

Aktualizacja() metoda wymusza aktualizację przetrwałego stanu obiektu w bazy danych, zawsze planowanie aktualizacji SQL.
...
Nie ma znaczenia, czy obiekt został zmodyfikowany przed lub po aktualizacji do ().
...
Hibernuj zawsze traktuje obiekt jako brudny i planuje aktualizację SQL., Która zostanie wykonana podczas opróżniania.

Z drugiej strony, merge() najpierw odpytuje bazę danych i nie wykonuje aktualizacji, jeśli stan się nie zmienił.

Tak więc, jeśli chcesz, aby Hibernate najpierw wysłał zapytanie do bazy danych, musisz użyć merge() (chociaż domyślne zachowanie update() może zostać przesłonięte przez specyfikację @org.hibernate.annotations.Entity(selectBeforeUpdate = true) na twoich obiektach).

+0

Dokładnie tego, czego szukałem - dzięki milionowi! – Jay

+0

FYI - Wpadłem na [ten post, który dalej rozwija różnice] (http://stackoverflow.com/questions/170962/nhibernate-difference-between-session-merge-and-session-saveorupdate) pomiędzy update() lub saveOrUpdate() i scal(). – Jay

1

spróbować dodać kolumnę rewizyjną (blokowanie optymistyczne) do podmiotów

@Version 
Date lastModified; 
+0

FYI - to mogło pomóc, ale nie mam kolumn wersji w moich bazach danych. – Jay

Powiązane problemy