2016-07-13 4 views
2

Je développe webapp avec Spring MVC et ont de telles méthodes dans mon application:condition de course entre les transactions

@Transactional 
public void methodA(Long id, String color) { 
    Fruit fruit = entityManager.createNamedQuery("Fruit.findById", Fruit.class).setParameter(1, id).getSingleResult(); 
    fruit.setColor("color"); 
    entityManager.merge(fruit); 
} 

@Transactional 
public void methodB(Long id, int price) { 
    Fruit fruit = entityManager.createNamedQuery("Fruit.findById", Fruit.class).setParameter(1, id).getSingleResult(); 
    fruit.setPrice(price); 
    entityManager.merge(fruit); 
} 

Ces deux méthodes sont souvent appelées presque en même temps, et à cause de cette condition de course se produit . Y a-t-il un moyen de résoudre ce problème? Je pense que mettre les deux dans une méthode synchronisée ne serait pas une bonne idée, parce que je m'attends à beaucoup d'appels de ces méthodes (des milliers) par différents utilisateurs en même temps, donc cela peut causer des retards. Fixe-moi si j'ai tort.

+0

Pour ce que ça vaut, votre requête complexe et coûteuse est juste 'entityManager.find (Fruit.class, id)'. – chrylis

+0

ces méthodes sont dans le service ou dans le DAO (ou référentiel)? –

+0

@KimAragonEscobar, il est à l'intérieur de la classe du référentiel. Et la classe de référentiel est dans la classe de service –

Répondre

0

EntityManager.merge(T entity) fusionne l'état de l'entité donnée dans le contexte de persistance actuel. En fonction du datastore sous-jacent, au moment où il fusionne l'entité, le même enregistrement d'entité provenant du référentiel peut déjà être modifié avec des informations différentes, de sorte que toute information altérée peut être perdue et écrasée par la fusion ultérieure. Au lieu d'utiliser EntityManager.merge(T entity), utilisez EntityManager.createQuery(CriteriaUpdate updateQuery).executeUpdate(). Cela devrait seulement mettre à jour les valeurs des attributs spécifiés que vous fournissez.

@Transactional 
public void methodA(Long id, String color) { 
    final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
    final CriteriaUpdate<Fruit> updateColor = cb.createCriteriaUpdate(Fruit.class); 
    final Root<Fruit> updateRoot = updateColor.from(Fruit.class); 
    updateColor.where(cb.equal(updateRoot.get(Fruit_.id), id)); 
    updateColor.set(updateRoot.get(Fruit_.id), id); 
    entityManager.createQuery(updateColor).executeUpdate(); 
} 

@Transactional 
public void methodB(Long id, int price) { 
    final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
    final CriteriaUpdate<Fruit> updatePrice = cb.createCriteriaUpdate(Fruit.class); 
    final Root<Fruit> updateRoot = updatePrice.from(Fruit.class); 
    updatePrice.where(cb.equal(updateRoot.get(Fruit_.id), id)); 
    updatePrice.set(updateRoot.get(Fruit_.price), price); 
    entityManager.createQuery(updatePrice).executeUpdate(); 
} 

Tant qu'aucune autre transaction met à jour les mêmes champs que l'une de ces méthodes, alors il ne devrait plus y avoir de problèmes avec cette mise à jour.

0

Les approches typiques pour faire face aux conditions de course sont locks. Dans un scénario pessimistic, vous interdisez à la base de données d'accepter des transactions sur une ressource si une autre transaction est actuellement active.

Une autre option est optimistic locking. Avant d'écrire une ressource, son état est comparé à celui qui était initialement lu. Si elles diffèrent, un autre processus a changé cette ressource, ce qui se termine généralement par un OptimisticLockException. La bonne chose est, vous pouvez l'attraper et réessayer de mettre à jour cette ressource tout de suite. Juste la même chose que vous pourriez dire à l'utilisateur sur le conflit. C'est ton choix.

Les deux solutions fonctionneraient bien pour votre cas d'utilisation. Lequel choisir dépend de nombreux facteurs. Je vous propose de lire sur les verrous et de choisir par vous-même.

Vous pouvez également considérer s'il est absolument nécessaire de valider immédiatement les ressources sur la base de données. Si vous vous attendez à ce qu'ils soient modifiés dans la prochaine seconde de toute façon, vous pouvez les stocker dans la mémoire et les vider toutes les n secondes, ce qui pourrait vous faire économiser un peu de temps système. Dans la plupart des cas, cette proposition est probablement une mauvaise idée. C'est juste une pensée sans avoir un aperçu plus profond de votre application.

0

En fonction de la réponse à cette question Would transactions/spring Transaction propagation solve this concurrency issue?, vous pouvez essayer de placer le @transactional sur le @service à la place de chaque méthode du référentiel. Vous auriez quelque chose comme ceci:

@Service 
@Transactional 
class MyService { 

    @Autowired 
    MyRepo repository; 
    public void methodA(Data data){ 
     repository.methodA(data); 
    } 
    public void methodB(Data data){ 
     repository.methodB(data); 
    } 
} 

Je sais que le problème de ce poste est pas la même chose que vous avez, mais cela pourrait résoudre votre problème.