2010-06-07 8 views
8

Sono in procinto di cambiare il mio codice JPA in giro per utilizzare i thread. Ho un gestore di entità e una transazione separate per ogni thread.Richiesta/salvataggio atomico JPA per l'applicazione multithreading

Quello che ho usato per avere (per l'ambiente filettata singola) era il codice come:

// get object from the entity manager 
X x = getObjectX(jpaQuery); 

if(x == null) 
{ 
    x = new X(); 
    x.setVariable(foo); 
    entityManager.persist(x); 
} 

Con quel codice in un ambiente multi-threaded sto ottenendo chiavi duplicate dal momento che, presumo, getObjectX restituisce null per un thread, quindi quel thread viene scambiato, il thread successivo chiama getObjextX, ottenendo anche null, e quindi entrambi i thread creeranno e persisteranno una nuova X().

corta di aggiungere nella sincronizzazione, c'è un modo per ottenere atomica/salva-se-Indifferente-esistere un valore con JPA o dovrei ripensare il mio approccio

EDIT:

sto usando l'ultima EclipseLink e MySql 5.1

EDIT 2:

ho aggiunto la sincronizzazione ... calo di prestazioni MASSIVE (al punto che essa non può essere utilizzato). Andando a raccogliere tutti i dati di nuovo sul thread principale e quindi fare le creazioni su quel thread.

risposta

4

breve risposta triste è l'API APP non può farlo per voi. L'intera API è più o meno costruita attorno al principio ottimistico di assumere che le cose funzionino e generare eccezioni in caso di modifiche simultanee.

Se ciò accade spesso, è probabile che qualche altro componente (qualunque cosa generi foo?) Che potrebbe trarre vantaggio dall'essere reso thread-safe, come forse un'alternativa alla sincronizzazione intorno alla query + creare.

+0

Devo farlo al contrario, ciò che crea Foo è fuori dal mio controllo ed è praticamente garantito avere duplicati. Dovrò raccogliere tutti i dati dai thread e poi archiviarli tutti in un unico thread. – TofuBeer

2

Penso che sarà necessario aggiungere un vincolo univoco sui campi utilizzati in "jpaQuery" in modo che il database non possa creare righe duplicate con gli stessi criteri utilizzati nei contrai per quella query. Il codice chiamante dovrà intercettare l'eccezione risultante che si verifica a seguito della violazione del vincolo (idealmente sarà una EntityExistsException, ma in questo caso la specifica non è chiara).

+0

Ho dei limiti univoci e sto ottenendo l'eccezione. Speravo di trovare un modo per farlo senza che ci fosse un'eccezione sollevata ... – TofuBeer

+1

L'utilizzo di un'eccezione è probabilmente la soluzione più semplice in questo scenario, è simile al blocco ottimistico che utilizza anche un'eccezione in caso di errore. –

0

Sei sicuro di aver bisogno di più gestori di entità? In una situazione simile, mi basta usare un EntityManager e semplici oggetti di blocco per-metodo:

private Object customerLock = new Object[0]; 

public Customer createCustomer(){ 
    Customer customer = new Customer(); 
    synchronized(customerLock){ 
     entityManager.persist(customer); 
    } 
    return customer; 
} 

Edit: OK, non si può fare molto su prestazioni, tranne dire che si comporta bene nelle mie applicazioni, ma per l'uso qualcosa di unicità del genere:

public Customer getOrCreateCustomer(String firstName, String lastName){ 
    synchronized(customerLock){ 
     List<Customer> customers = 
      entityManager.createQuery(
       "select c from Customer c where c.firstName = :firstName" 
       + " and c.lastName = :lastName" 
      ) 
      .setParam("firstName", firstName) 
      .setParam("lastName", lastName) 
      .setMaxResults(1) 
      .getResultList(); 
     if(customers.isEmpty()){ 
      Customer customer = new Customer(firstName, lastName); 
      entityManager.persist(customer); 
     }else{ 
      customer = customers.get(0); 
     } 
    } 
    return customer; 
} 
+0

Ho bisogno di assicurarmi che l'oggetto, nel tuo caso, il Cliente non esista già. Ho provato ad aggiungere la sincronizzazione, ma le prestazioni erano di gran lunga peggiori. – TofuBeer

+0

La sincronizzazione a livello di Java non funzionerà nel cluster – Vadzim

3

Alcuni "hack" per prendere in considerazione:

  • attuare hashCode() e equals() in base alla chiave di business degli oggetti (non l'id generato)
  • Sincronizza:

    (obj.getClass().getName() + String.valueOf(hashCode())).intern() 
    

In questo modo si ottengono blocchi solo nei casi rilevanti.

+0

Hmmm ... Ho già un codice equals/hash simile ... Non ho mai pensato di sincronizzarli. Darò un colpo solo per vedere. – TofuBeer

+0

è intelligente :-) –

+0

Questo non funzionerà nel cluster – Vadzim