交易之间的竞争条件

时间:2016-07-13 20:25:40

标签: java spring hibernate transactions race-condition

我正在使用Spring MVC开发webapp,并在我的应用程序中使用了这样的方法:

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

这两种方法通常几乎同时被调用,因此会出现竞争条件。有没有办法解决这个问题?我认为将它们放在一个同步方法中并不是一个好主意,因为我期望不同用户同时调用这些方法(数千个),因此可能会导致延迟。如果我错了,请修理我。

3 个答案:

答案 0 :(得分: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();
}

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

答案 1 :(得分:0)

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

另一个选项是pessimistic。在写入资源之前,将其状态与最初读取时的状态进行比较。如果它们不同,则另一个进程已更改该资源,该资源通常以optimistic locking结尾。好消息是,您可以抓住它并立即重新尝试更新该资源。就像你可以告诉用户冲突一样。这是你的选择。

这两种解决方案都适用于您的用例。选择哪一个取决于许多因素。我建议你读一下锁并自己选择。

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

答案 2 :(得分: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);
    }
}

我知道这篇文章的问题与你的不一样,但这可以解决你的问题。