2016-07-13 36 views
2

我正在开发使用Spring MVC Web应用程序,并在我的应用这样的方法:竞争条件

@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); 
} 

这两种方法往往是在同一时间差点叫,正因为这样的竞争条件发生。有没有办法解决这个问题?我认为把它们放在一个同步的方法中并不是一个好主意,因为我期望不同用户同时调用这些方法(数千个)很多,所以会导致延迟。修复我,如果我错了。

+0

值得一提的是,您复杂而昂贵的查询只是'entityManager.find(Fruit.class,id)'。 – chrylis

+0

这种方法是在服务中还是在DAO(或存储库)中? –

+0

@KimAragonEscobar,它位于存储库类中。存储库类在服务类中 –

回答

0

EntityManager.merge(T entity)将给定实体的状态合并到当前持久化上下文中。根据底层数据存储区,在合并实体时,存储库中的同一实体记录可能已被更改为不同的信息,因此任何更改后的信息可能会丢失并被后面的合并覆盖。

而不是使用EntityManager.merge(T entity),请使用EntityManager.createQuery(CriteriaUpdate updateQuery).executeUpdate()。这应该只更新您提供的指定属性的值。

@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(); 
} 

只要没有其他事务与这些方法中的任何一个更新相同的字段,那么应该不会再有任何此更新的问题。

0

处理竞赛条件的典型方法是locks。在pessimistic方案中,如果另一个事务当前处于活动状态,您将禁止数据库接受资源上的任何事务。

另一种选择是optimistic locking。在写回资源之前,将其状态与初始读取时的状态进行比较。如果它们不同,另一个过程已更改该资源,通常以OptimisticLockException结束。好处是,您可能会抓住它并立即重新更新该资源。就像你可以告诉用户有关冲突一样。这是你的选择。

这两种解决方案都适合您的使用案例。选择哪一个取决于许多因素。我会建议你阅读锁,然后自己选择。

您可能还想考虑是否有必要立即将资源提交给数据库。如果你希望它们在接下来的第二秒内被修改,你可以将它们存储在内存中并每隔n秒刷新一次,这可以为你节省一些数据库开销。在大多数情况下,这个提议可能是一个坏主意。这只是一个没有更深入洞察你的应用程序的想法。

0

根据此问题的答案Would transactions/spring Transaction propagation solve this concurrency issue?您可以尝试将@transactional放在@service上,而不是来自存储库中的每个方法。 您将有这样的事情:

@Service 
@Transactional 
class MyService { 

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

我知道,从这个职位的问题不在于你有相同的,但是这可能会解决你的问题。