2011-01-18 15 views
11

Ho due bean di entità definite come segue (roba non correlato rimosso):arresto Hibernate di aggiornare raccolte quando non hanno cambiato

@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 è definito come segue:

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

E nel mio Codice DAO - Ho questi metodi:

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

Quando carico un oggetto MasterItem, viene caricato correttamente, un d carica anche i CriticalItems Set con i dati, come indicato. Quindi, invio questi dati all'interfaccia utente e ottengo una copia aggiornata, che cerco quindi di mantenere. L'utente aggiorna i campi nell'oggetto MasterItem, ma non tocca il set di CriticalItems o qualcosa in esso contenuto - rimane invariato.

Quando viene richiamato il metodo save(), Hibernate richiede di inviare aggiornamenti SQL per ogni elemento nel Set di CriticalItems, anche se nessuno di essi è stato modificato in alcun modo.

Dopo aver scavato, ecco cosa penso stia succedendo. Quando eseguo un saveOrUpdate(), Hibernate vede il mio oggetto MasterItem in uno stato disconnesso, quindi tenta di ricaricarlo dal disco. Tuttavia, quando lo fa, sembra che stia usando un'istruzione preparata (che è stata creata automaticamente da Hibernate all'avvio) e questa istruzione preparata non tenta di unirsi ai dati di CriticalItems.

Quindi Hibernate ha il mio oggetto MasterItem aggiornato con un set completo di CriticalItems, ma usa un oggetto MasterItem senza raccolte come oggetto "previousState". Pertanto, tutti i CriticalItem vengono aggiornati tramite SQL (non inserito, che è di per sé interessante).

Ho fatto qualcosa nelle mie annotazioni che ha causato questo comportamento? So che posso usare un Interceptor per scoprire che l'oggetto è davvero cambiato, o cambiato il flag dirty per sovrascrivere l'algoritmo predefinito di Hibernate - ma questo sembra essere qualcosa che Hibernate dovrebbe gestire da solo senza il mio intervento.

Qualsiasi intuizione sarebbe apprezzata.

UPDATE: Sulla base di commenti, credo di capire la differenza tra saveOrUpdate() e unire(). Mi rendo conto che saveOrUpdate() comporterà in entrambi i casi un INSERT SQL o un UPDATE SQL e che, in teoria, l'unione genererà aggiornamenti solo se l'oggetto è cambiato dal suo stato persistente, ma per determinarlo, Hibernate deve ricaricare l'oggetto prima tramite SQL SELECT.

Quindi, ho pensato di poter tornare al mio codice e modificare saveOrUpdate() per unire() e funzionerebbe, ma non era proprio il caso.

quando ho usato merge(), mi è stato sempre

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

ma ha funzionato bene se ho cambiato di nuovo a saveOrUpdate().

Ho finalmente scoperto perché - Non ho incluso CascadeType.MERGE nella mia annotazione @Cascade (ugh). Una volta risolto, l'eccezione è andata via.

+0

avete una colonna di blocco ottimistica (una colonna annotata con @version con tipo data od int) – lweller

risposta

16

Questa è la differenza semantica tra il update() e merge().

Da Christian Bauer and Gavin King's Java Persistence with Hibernate (non riesco a trovare chiara spiegazione di questo comportamento nei documenti Hibernate):

Il metodo update() costringe un aggiornamento per il persistente stato di oggetto nel database, sempre pianificare un UPDATE SQL.
...
Non importa se l'oggetto oggetto viene modificato prima o dopo che è passato a update().
...
Hibernate considera sempre l'oggetto come sporco e pianifica un UPDATE SQL., Che verrà eseguito durante lo svuotamento .

D'altra parte, merge() interroga il database prima, e non esegue l'aggiornamento se lo stato non sono cambiate.

Quindi, se si vuole Hibernate per interrogare il database prima, è necessario utilizzare merge() (anche se il comportamento di default di update() può essere sovrascritto da specificando @org.hibernate.annotations.Entity(selectBeforeUpdate = true) sulle proprie entità).

+0

Esattamente quello che stavo cercando - grazie mille! – Jay

+0

FYI - Mi sono imbattuto in [questo post che elabora ulteriormente le differenze] (http://stackoverflow.com/questions/170962/nhibernate-difference-between-session-merge-and-session-saveorupdate) tra update() o saveOrUpdate() e unione(). – Jay

1

tenta di aggiungere una colonna di revisione (il blocco ottimistico) per i soggetti

@Version 
Date lastModified; 
+0

FYI - questo potrebbe aver aiutato, ma non ho colonne di versione nei miei database. – Jay

Problemi correlati