Qual è il modo corretto di riattaccare gli oggetti staccati in Hibernate?

Ho una situazione in cui devo ricolbind gli oggetti distaccati a una sessione di sospensione, sebbene un object della stessa identity framework possa già esistere nella sessione, il che causerà errori.

In questo momento, posso fare una delle due cose.

  1. getHibernateTemplate().update( obj ) Funziona se e solo se un object non esiste già nella sessione di ibernazione. Le eccezioni vengono lanciate dichiarando un object con l’identificatore dato già esistente nella sessione quando ne ho bisogno in seguito.

  2. getHibernateTemplate().merge( obj ) Funziona se e solo se esiste un object nella sessione di ibernazione. Le eccezioni vengono generate quando ho bisogno che l’object si trovi in ​​una sessione dopo se lo uso.

Dati questi due scenari, come posso associare genericamente le sessioni agli oggetti? Non voglio usare eccezioni per controllare il stream della soluzione di questo problema, poiché deve esserci una soluzione più elegante …

Quindi sembra che non ci sia modo di ricolbind una entity framework stantia in JPA.

merge() invierà lo stato precedente al DB e sovrascriverà eventuali aggiornamenti intermedi.

refresh() non può essere chiamato su un’ quadro distaccata.

lock() non può essere chiamato su un’ quadro distaccata, e anche se potesse, e ha ricollegato l’ quadro, chiamando ‘lock’ con l’argomento ‘LockMode.NONE’ che implica che stai bloccando, ma non bloccando, è il pezzo più controintuitivo del design API che abbia mai visto.

Quindi sei bloccato. C’è un metodo detach() , ma non attach() o reattach() . Un passaggio ovvio nel ciclo di vita dell’object non è disponibile.

A giudicare dal numero di domande simili sull’APP, sembra che anche se JPA affermasse di avere un modello coerente, sicuramente non corrisponde al modello mentale della maggior parte dei programmatori, che sono stati maledetti per perdere molte ore a cercare di capire come ottenere JPA per fare le cose più semplici e finire con il codice di gestione della cache su tutte le loro applicazioni.

Sembra che l’unico modo per farlo sia scartare la tua quadro stantia stantia e fare una query di ricerca con lo stesso id, che colpirà la L2 o il DB.

Mik

Tutte queste risposte mancano di un’importante distinzione. update () è usato per (ri) albind il grafico dell’object a una sessione. Gli oggetti che passi sono quelli che sono stati gestiti.

unire () in realtà non è un’API (re) allegato. Avviso merge () ha un valore di ritorno? Questo perché ti restituisce il grafico gestito, che potrebbe non essere il grafico che hai passato. merge () è un’API JPA e il suo comportamento è regolato dalle specifiche JPA. Se l’object passato per unire () è già gestito (già associato alla Sessione), allora questo è il grafico con cui lavora Hibernate; l’object passato è lo stesso object restituito da unire (). Se, tuttavia, l’object passato in unione () viene rimosso, Hibernate crea un nuovo object grafico che viene gestito e copia lo stato dal grafico separato sul nuovo grafico gestito. Di nuovo, questo è tutto dettato e regolato dalle specifiche JPA.

In termini di strategia generica per “assicurarsi che questa entity framework sia gestita, o gestirla”, dipende in qualche modo se si desidera rendere conto anche dei dati non ancora inseriti. Supponendo che tu faccia, usa qualcosa come

 if ( session.contains( myEntity ) ) { // nothing to do... myEntity is already associated with the session } else { session.saveOrUpdate( myEntity ); } 

Avviso Ho usato saveOrUpdate () piuttosto che update (). Se non vuoi che i dati non ancora inseriti siano gestiti qui, usa update () invece …

Risposta non polemica: stai probabilmente cercando un contesto di persistenza esteso. Questa è una delle ragioni principali dietro il framework Seam … Se stai lottando per usare Hibernate in spring in particolare, dai un’occhiata a questo pezzo dei documenti di Seam.

Risposta diplomatica: è descritta nei documenti di Hibernate . Se hai bisogno di ulteriori chiarimenti, dai un’occhiata alla Sezione 9.3.2 di Java Persistence con Hibernate chiamata “Lavorare con oggetti distaccati”. Consiglio vivamente di ottenere questo libro se stai facendo qualcosa di più di CRUD con Hibernate.

Se sei sicuro che la tua quadro non sia stata modificata (o se sei d’accordo che qualsiasi modifica andrà persa), allora puoi ricollegarla alla sessione con il lucchetto.

 session.lock(entity, LockMode.NONE); 

Non bloccherà nulla, ma otterrà l’entity framework dalla cache di sessione o (se non trovata li) la leggerà dal DB.

È molto utile prevenire LazyInitException quando si naviga in relazioni da una “vecchia” (dalle quadro HttpSession per esempio). Per prima cosa “ri-albind” l’entity framework.

L’utilizzo di get può anche funzionare, tranne quando si eredita l’ereditarietà (che genererà già un’eccezione sul getId ()).

 entity = session.get(entity.getClass(), entity.getId()); 

Sono tornato a JavaDoc per org.hibernate.Session e ho trovato quanto segue:

Le istanze transitori possono essere rese persistenti chiamando save() , persist() o saveOrUpdate() . Le istanze persistenti possono essere rese transitori chiamando delete() . Qualsiasi istanza restituita da un metodo get() o load() è persistente. Le istanze saveOrUpdate() possono essere rese persistenti chiamando update() , saveOrUpdate() , lock() o replicate() . Lo stato di un’istanza transitoria o distaccata può anche essere reso persistente come una nuova istanza persistente chiamando merge() .

Così update() , saveOrUpdate() , lock() , replicate() e merge() sono le opzioni candidate.

update() : genera un’eccezione se esiste un’istanza persistente con lo stesso identificativo.

saveOrUpdate() : salva o aggiorna

lock() : deprecato

replicate() : persiste lo stato dell’istanza distaccata, riutilizzando il valore corrente dell’identificatore.

merge() : restituisce un object persistente con lo stesso identificativo. L’istanza data non viene associata alla sessione.

Quindi, lock() non deve essere utilizzato immediatamente e in base al requisito funzionale è ansible scegliere uno o più di essi.

L’ho fatto in quel modo in C # con NHibernate, ma dovrebbe funzionare allo stesso modo in Java:

 public virtual void Attach() { if (!HibernateSessionManager.Instance.GetSession().Contains(this)) { ISession session = HibernateSessionManager.Instance.GetSession(); using (ITransaction t = session.BeginTransaction()) { session.Lock(this, NHibernate.LockMode.None); t.Commit(); } } } 

First Lock è stato chiamato su ogni object perché Contains era sempre falso. Il problema è che NHibernate confronta gli oggetti per id e tipo di database. Contiene usa il metodo equals , che si confronta per riferimento se non viene sovrascritto. Con quel metodo equals funziona senza eccezioni:

 public override bool Equals(object obj) { if (this == obj) { return true; } if (GetType() != obj.GetType()) { return false; } if (Id != ((BaseObject)obj).Id) { return false; } return true; } 

Session.contains(Object obj) controlla il riferimento e non rileverà un’istanza diversa che rappresenta la stessa riga e che è già associata ad essa.

Ecco la mia soluzione generica per Entità con una proprietà identificatore.

 public static void update(final Session session, final Object entity) { // if the given instance is in session, nothing to do if (session.contains(entity)) return; // check if there is already a different attached instance representing the same row final ClassMetadata classMetadata = session.getSessionFactory().getClassMetadata(entity.getClass()); final Serializable identifier = classMetadata.getIdentifier(entity, (SessionImplementor) session); final Object sessionEntity = session.load(entity.getClass(), identifier); // override changes, last call to update wins if (sessionEntity != null) session.evict(sessionEntity); session.update(entity); } 

Questo è uno dei pochi aspetti di .Net EntityFramework che mi piace, le diverse opzioni di collegamento riguardanti le quadro modificate e le loro proprietà.

Ho trovato una soluzione per “aggiornare” un object dall’archivio di persistenza che terrà conto di altri oggetti che potrebbero già essere allegati alla sessione:

 public void refreshDetached(T entity, Long id) { // Check for any OTHER instances already attached to the session since // refresh will not work if there are any. T attached = (T) session.load(getPersistentClass(), id); if (attached != entity) { session.evict(attached); session.lock(entity, LockMode.NONE); } session.refresh(entity); } 

Scusa, non riesco ad aggiungere commenti (ancora?).

Utilizzo di Hibernate 3.5.0-Final

Mentre il metodo Session#lock è deprecato, javadoc suggerisce di utilizzare Session#buildLockRequest(LockOptions)#lock(entity) e se si assicura che le associazioni abbiano cascade=lock , neanche il caricamento lento è un problema.

Quindi, il mio metodo di collegamento sembra un po ‘come

 MyEntity attach(MyEntity entity) { if(getSession().contains(entity)) return entity; getSession().buildLockRequest(LockOptions.NONE).lock(entity); return entity; 

I test iniziali suggeriscono che funziona bene.

prova getHibernateTemplate (). replica (entity, ReplicationMode.LATEST_VERSION)

Nel post originale ci sono due metodi, update(obj) e merge(obj) menzionati per funzionare, ma in circostanze opposte. Se questo è veramente vero, allora perché non provare per vedere se l’object è già nella sessione e poi chiamare update(obj) se lo è, altrimenti chiama merge(obj) .

Il test per l’esistenza nella sessione è session.contains(obj) . Pertanto, penserei che il seguente pseudo-codice funzionerebbe:

 if (session.contains(obj)) { session.update(obj); } else { session.merge(obj); } 

Forse si comporta in modo leggermente diverso su Eclipselink. Per riattaccare oggetti distaccati senza ottenere dati obsoleti, di solito faccio:

 Object obj = em.find(obj.getClass(), id); 

e come facoltativo un secondo passo (per ottenere l’invalidazione delle cache):

 em.refresh(obj) 

per ricolbind questo object, è necessario utilizzare merge ();

questo metodo accetta in parametro l’ quadro distaccata e restituisce un’ quadro che verrà allegata e ricaricata dal Database.

 Example : Lot objAttach = em.merge(oldObjDetached); objAttach.setEtat(...); em.persist(objAttach); 

chiamare first merge () (per aggiornare l’istanza persistente), quindi bloccare (LockMode.NONE) (per colbind l’istanza corrente, non quella restituita da merge ()) sembra funzionare per alcuni casi d’uso.

 try getHibernateTemplate().saveOrUpdate()