【问题标题】:Race condition between transactions事务之间的竞争条件
【发布时间】:2016-11-16 14:20:08
【问题描述】:

我正在使用 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);
}

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

【问题讨论】:

  • 不管怎样,您的复杂而昂贵的查询只是entityManager.find(Fruit.class, id)
  • 这些方法是在服务中还是在 DAO(或存储库)中?
  • @KimAragonEscobar ,它在存储库类中。并且存储库类在服务类中

标签: java spring hibernate transactions race-condition


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      根据这个问题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);
          }
      }
      

      我知道这篇文章中的问题与您的问题不同,但这可以解决您的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-10
        • 2016-02-02
        • 2013-11-14
        • 1970-01-01
        • 2021-07-30
        相关资源
        最近更新 更多