2010-06-07 3 views
8

Je suis en train de changer mon code JPA pour faire usage de threads. J'ai un gestionnaire d'entité séparé et une transaction pour chaque thread.JPA demande atomique/enregistrer pour l'application multithread

Ce que je l'habitude d'avoir (pour l'environnement seul thread) est un code comme:

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

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

Avec ce code dans l'environnement multithread Je reçois des clés en double depuis, je suppose, getObjectX retourne null pour une thread, puis ce thread est transféré, le thread suivant appelle getObjextX, obtenant également null, puis les deux threads vont créer et conserver un nouveau X().

Court d'ajouter en synchronisation, est-il un moyen atomique pour obtenir/sauvegarde si-ne-exister avec une valeur JPA ou devrais-je revoir mon approche

EDIT:

J'utilise la dernière EclipseLink et MySql 5.1

EDIT 2:

J'ajouté hit synchronisation ... performances MASSIVE (au point qu'il ne peut pas être utilisé). Aller à rassembler toutes les données sur le fil principal et ensuite faire les créations sur ce fil.

Répondre

4

réponse courte triste est pas l'API JPA ne peut le faire pour vous. L'ensemble de l'API est plus ou moins construit autour du principe optimiste de supposer que les choses vont fonctionner et de jeter des exceptions en cas de modification simultanée.

Si cela se produit souvent, il y a probablement un autre composant (quoi que génère foo?) Qui pourrait bénéficier d'être threadsafe, peut-être une alternative à la synchronisation autour de la requête + create.

+0

Je dois le faire dans le sens inverse, ce qui crée Foo est hors de mon contrôle, et il est pratiquement garanti d'avoir des doublons. Je vais devoir rassembler toutes les données des threads, puis les stocker dans un seul thread. – TofuBeer

2

Je pense que vous devrez ajouter une contrainte unique sur les champs utilisés dans "jpaQuery" afin que la base de données ne puisse pas créer de lignes dupliquées avec les mêmes critères utilisés dans les contraintes pour cette requête. Le code appelant devra piéger l'exception résultante qui se produit à la suite de la violation de contrainte (idéalement, il s'agira d'une EntityExistsException, mais la spécification n'est pas claire dans ce cas).

+0

J'ai des contraintes uniques, et je reçois l'exception. J'espérais trouver un moyen de le faire sans qu'une exception ne soit levée ... – TofuBeer

+1

L'utilisation d'une exception est probablement la solution la plus simple dans ce scénario, elle est similaire au verrouillage optimiste qui utilise également une exception en cas d'échec. –

0

Êtes-vous sûr de vouloir utiliser plusieurs gestionnaires d'entités? Dans une situation similaire, je viens d'utiliser un entitymanager et simples objets de verrouillage par méthode:

private Object customerLock = new Object[0]; 

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

Edit: OK, ne peut pas faire grand-chose sur la performance, sauf en disant qu'il fonctionne ok dans mes applications, mais pour une utilisation unique quelque chose comme ceci:

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

Je dois m'assurer que l'objet, dans votre cas, client n'existe pas déjà. J'ai essayé d'ajouter de la synchronisation mais la performance était loin d'être bonne. – TofuBeer

+0

La synchronisation au niveau Java ne fonctionnera pas dans le cluster – Vadzim

3

Certains "hack" à considérer:

  • mettre en œuvre hashCode() et equals() en fonction de la clé métier des objets (et non l'identifiant généré)
  • synchronisent sur:

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

Ainsi, vous n'obtiendrez des verrous que dans les cas appropriés.

+0

Hmmm ... J'ai déjà un tel code égal/hash ... Je n'ai jamais pensé à les synchroniser. Je vais essayer juste pour voir. – TofuBeer

+0

c'est intelligent :-) –

+0

Cela ne fonctionnera pas en cluster – Vadzim