2013-02-26 13 views
17

Sto usando SEAM con JPA (implementato come Seam Managed Persistance Context), nel mio backing bean carico una raccolta di entità (ArrayList) nel backing bean.Forza aggiornamento della raccolta JPA entityManager

Se un altro utente modifica una delle entità in una sessione diversa Voglio che questi cambiamenti siano propagate alla raccolta in mia sessione, ho un metodo di refreshList() e provato quanto segue ...

@Override 
public List<ItemStatus> refreshList(){ 
    itemList = itemStatusDAO.getCurrentStatus(); 
} 

Con la seguente query

@SuppressWarnings("unchecked") 
@Override 
public List<ItemStatus> getCurrentStatus(){ 
    String s = "SELECT DISTINCT iS FROM ItemStatus iS "; 
    s+="ORDER BY iS.dateCreated ASC"; 
    Query q = this.getEntityManager().createQuery(s); 
    return q.getResultList(); 
} 

Re-esecuzione della query, questo restituisce solo gli stessi dati ho già (suppongo che sta usando la cache di 1 ° livello, piuttosto che colpire il database)

@Override 
public List<ItemStatus> refreshList(){ 
    itemStatusDAO.refresh(itemList) 
} 

Calling entityManager.refresh(), questo dovrebbe aggiornare dal database tuttavia ottengo un'eccezione javax.ejb.EJBTransactionRolledbackException: Entity not managed quando uso questo, normalmente io userei entityManager.findById(entity.getId) prima di chiamare refresh() per assicurarsi che sia collegato al PC, ma come sto rinfrescanti una raccolta di entità non posso farlo.

Sembra un problema abbastanza semplice, non riesco a credere che non ci sia modo di forzare JPA/hibernate per bypassare la cache e colpire il database ?!

TEST AGGIORNAMENTO CASO:

Sto utilizzando due browser diversi (1 e 2) per caricare la stessa pagina web, faccio una modifica in 1 che aggiorna un attributo booleano in una delle entità ItemStatus, la vista viene aggiornato per 1 per visualizzare l'attributo aggiornato, controllo il database tramite PGAdmin e la riga è stata aggiornata. Quindi premere Aggiorna nel Browser 2 e l'attributo non è stato aggiornato

Ho provato a utilizzare il seguente metodo per unire tutte le entità prima di chiamare .refresh, ma le entità non erano ancora aggiornate dal database.

@Override 
public void mergeCollectionIntoEntityManager(List<T> entityCollection){ 
    for(T entity: entityCollection){ 
     if(!this.getEntityManager().contains(entity)){ 
      this.getEntityManager().refresh(this.getEntityManager().merge(entity)); 
     } 
    } 
} 
+0

Hai unificato gli oggetti nel gestore di entità prima di chiamare l'aggiornamento? – Perception

+0

No, non l'ho fatto, il metodo merge() non accetta collezioni e pensavo che chiamarlo su ogni membro della collezione fosse un po 'eccessivo? – DaveB

+0

Potrebbe essere, se si dispone di una lista davvero grande. Ma le normali fusioni sincronizzano semplicemente lo stato dell'oggetto con l'unità di lavoro dei gestori di entità e sono molto veloci. Se la fusione impiega troppo tempo, puoi sempre avviare tutto dalla cache di secondo livello con 'entityMgr.getEntityManagerFactory(). GetCache(). EvictAll()'. – Perception

risposta

24

You' avendo due problemi separati qui. Prendiamo per primo quello facile.


javax.ejb.EJBTransactionRolledbackException: Ente non è riuscito

La List di oggetti restituiti da tale query non è un Entity, e quindi non si può .refresh esso. In effetti, questo è ciò di cui si lamenta l'eccezione. Stai chiedendo allo EntityManager di fare qualcosa con un oggetto che semplicemente non è un noto Entity.

Se si desidera .refresh un sacco di cose, scorrere attraverso di loro e .refresh singolarmente.


Aggiornamento dell'elenco dei ItemStatus

Stai interagire con Session di cache-level di Hibernate in un modo che, dalla tua domanda, che non ti aspetti. Dalla Hibernate docs:

Per gli oggetti ad una particulare Session (cioè nell'ambito di un sessione) ... identità JVM per l'identità del database è garantita da Sospensione.

L'impatto di questo numero su Query.getResultList() è che non si ottiene necessariamente lo stato più aggiornato del database.

Il Query eseguito esegue davvero un elenco di ID entità corrispondenti a tale query. Tutti gli ID già presenti nella cache Session sono associati alle entità note, mentre tutti gli ID che non sono vengono popolati in base allo stato del database. Le entità precedentemente note non sono state aggiornate dal database.

Ciò significa che, nel caso in cui, tra le due esecuzioni del Query dall'interno della stessa transazione, alcuni dati è cambiato nel database per una determinata entità conosciuta, la seconda query avrebbe non raccogliere che il cambiamento . Tuttavia, prenderebbe una nuova istanza ItemStatus (a meno che non stiate usando un query cache, che presumo che tu non sia).

Per farla breve: con Hibernate, ogni volta che si vuole, all'interno di una singola transazione, caricare un'entità e quindi prendere ulteriori modifiche a tale entità dal database, è necessario in modo esplicito .refresh(entity).

Come si desidera gestire questo dipende un po 'dal vostro caso d'uso. Due opzioni che posso pensare fuori del blocco:

  1. abbiamo sono di avere il DAO legata alla durata della transazione, e pigramente inizializzare il List<ItemStatus>. Le chiamate successive a DAO.refreshList iterano attraverso List e .refresh(status). Se sono necessarie anche entità appena aggiunte, è necessario eseguire Query e anche aggiornare gli oggetti ItemStatus noti.
  2. Avvia una nuova transazione. Sembra proprio dalla tua chat con @Perception che non è un'opzione.

Alcune note aggiuntive

C'era discussione su utilizzando i suggerimenti di query. Ecco il motivo per cui non ha funzionato:

org.hibernate.cacheable = false Questo sarebbe solo rilevante se si stesse utilizzando un query cache, che è raccomandato solo in circostanze molto particolari. Anche se lo stavi utilizzando, ciò non influirebbe sulla tua situazione perché la cache della query contiene ID oggetto, non dati.

org.hibernate.cacheMode = REFRESH Questa è una direttiva di Hibernate second-level cache. Se la cache di secondo livello fosse attiva, E stavi emettendo le due query da transazioni diverse, allora avresti ottenuto dati obsoleti nella seconda query e questa direttiva avrebbe risolto il problema. Ma se ti trovi nella stessa sessione nelle due query, la cache di secondo livello entrerebbe in gioco solo per evitare il caricamento del database per le entità nuove a questo Session.

+1

Grazie per la risposta dettagliata, molto utile! Ora capisco perché le entità non sono aggiornate, ma quando eseguo l'iterazione dell'elenco e chiamo .refresh() su ciascun elemento, non mi danno ancora i valori aggiornati, per rispondere alla domanda precedente Sto usando Hibernate 3.6.0, I non sono sicuro di come verificare se sono nuove istanze o no (presumibilmente potrei controllare il codice hash per l'uguaglianza?). – DaveB

+1

Per il controllo della nuova istanza, usa solo ==, vuoi sapere se gli oggetti sono gli stessi. Per .refresh, ciò significa quasi certamente che le impostazioni di isolamento della transazione non sono quelle che ritieni siano. Puoi impostarlo esplicitamente su READ-COMMITED? – sharakan

+1

Per fare ciò, modifica le proprietà della connessione Hibernate per impostare hibernate.connection.isolation = 2 – sharakan

1

dovete refersh entità che è stato restituito dal metodo entityManager.merge(), qualcosa di simile a:

@Override 
public void refreshCollection(List<T> entityCollection){ 
    for(T entity: entityCollection){ 
     if(!this.getEntityManager().contains(entity)){ 
      this.getEntityManager().refresh(this.getEntityManager().merge(entity)); 
     } 
    } 
} 

In questo modo si dovrebbe sbarazzarsi di javax.ejb.EJBTransactionRolledbackException: Entity not managed eccezione.

UPDATE

Forse è più sicuro di tornare nuova collezione:

public List<T> refreshCollection(List<T> entityCollection) 
    { 
     List<T> result = new ArrayList<T>(); 
     if (entityCollection != null && !entityCollection.isEmpty()) { 
      getEntityManager().getEntityManagerFactory().getCache().evict(entityCollection.get(0).getClass()); 
      T mergedEntity; 
      for (T entity : entityCollection) { 
       mergedEntity = entityManager.merge(entity); 
       getEntityManager().refresh(mergedEntity); 
       result.add(mergedEntity); 
      } 
     } 
     return result; 
    } 

oppure si può essere più efficace se si può accedere agli ID di entità simile a questo:

public List<T> refreshCollection(List<T> entityCollection) 
    { 
     List<T> result = new ArrayList<T>(); 
     T mergedEntity; 
     for (T entity : entityCollection) { 
      getEntityManager().getEntityManagerFactory().getCache().evict(entity.getClass(), entity.getId()); 
      result.add(getEntityManager().find(entity.getClass(), entity.getId())); 
     } 
     return result; 
    } 
+0

Ho provato questo e impedisce l'eccezione, ma non mi dà le entità aggiornate dal database – DaveB

+0

Ho aggiornato la mia risposta. –

+0

Ho provato anche questo, ma nel mio caso, non ho nemmeno visto la query di Hibernate da aggiornare. Inoltre, l'entità non riflette anche i valori generati dal DB. – user2250246

1

Prova marcatura @Transactional

@Override 
@org.jboss.seam.annotations.Transactional 
public void refreshList(){ 
    itemList = em.createQuery("...").getResultList(); 
} 
0

una delle opzioni - la cache JPA bypass per uno specifico i risultati delle query:

// force refresh results and not to use cache 

    query.setHint("javax.persistence.cache.storeMode", "REFRESH"); 

molti altri suggerimenti di ottimizzazione e di configurazione possono essere trovati sul sito http://docs.oracle.com/javaee/6/tutorial/doc/gkjjj.html

0

Dopo aver eseguito correttamente il debug, mi sono imbattuto in uno degli sviluppatori del mio team che lo ha iniettato nel livello DAO as-

@Repository 
public class SomeDAOImpl 

    @PersistenceContext(type = PersistenceContextType.EXTENDED) 
    private EntityManager entityManager; 

Così, per consolidare si stava facendo nella cache e l'uso query per restituire gli stessi dati non aggiornati, anche se i dati sono stati modificati in una delle colonne delle tabelle coinvolte nella query SQL nativo.

Problemi correlati