事务之间的竞态条件

4

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

这两种方法通常同时被调用,这导致了竞态条件。有没有办法解决这个问题?我认为把它们都放在一个同步的方法中并不是一个好主意,因为我预计这些方法会被许多不同用户同时调用(数千次),所以可能会导致延迟。如果我错了,请纠正我。

说句实话,你那复杂而昂贵的查询只需写成entityManager.find(Fruit.class, id) - chrylis -cautiouslyoptimistic-
这些方法是在Service中还是在DAO(或repository)中? - Kim Aragon Escobar
@KimAragonEscobar,它在存储库类中。而存储库类位于服务类中。 - red_white_code
3个回答

1
典型处理竞态条件的方法是使用锁。在悲观情况下,如果有其他事务正在进行中,您将禁止数据库接受资源上的任何事务。
另一个选择是乐观锁定。在将资源写回之前,将其状态与最初读取时的状态进行比较。如果它们不同,则另一个进程已更改该资源,这通常会导致OptimisticLockException。好处是,您可以捕获它并立即重试更新该资源。同样,您可以告诉用户有冲突发生了。这是您的选择。
这两种解决方案都适用于您的用例。选择哪个取决于许多因素。我建议您阅读有关锁的信息,并自行选择。
您还可以考虑是否绝对必要立即将资源提交到数据库。如果您预计它们在下一秒钟内被修改,您可以将它们存储在内存中,并每n秒刷新一次,这可能会为您节省一些数据库开销。在大多数情况下,这个提议可能是一个坏主意。这只是一个想法,没有深入了解您的应用程序。

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
根据这个问题的答案事务/春季事务传播是否可以解决并发问题?,您可以尝试将@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);
    }
}

我知道这篇文章中的问题与你遇到的不同,但这可能会解决你的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接