Java 事务之间的竞争条件

Java 事务之间的竞争条件,java,spring,hibernate,transactions,race-condition,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");

我正在使用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.merge(T实体)将给定实体的状态合并到当前持久性上下文中。根据基础数据存储,在合并实体时,存储库中的同一实体记录可能已经用不同的信息进行了更改,因此任何更改后的信息都可能丢失,并在以后的合并中被覆盖

不要使用
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();
}
@Transactional
公共无效方法A(长id,字符串颜色){
最终CriteriaBuilder cb=entityManager.getCriteriaBuilder();
final CriteriaUpdate updateColor=cb.createCriteriaUpdate(Fruit.class);
最终根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();
}
@交易的
公共无效方法B(长id,整数价格){
最终CriteriaBuilder cb=entityManager.getCriteriaBuilder();
final CriteriaUpdate updatePrice=cb.createCriteriaUpdate(Fruit.class);
最终根updateRoot=updatePrice.from(Fruit.class);
updatePrice.where(cb.equal(updateRoot.get(Fruit.id),id));
updatePrice.set(updateRoot.get(水果价格),price);
entityManager.createQuery(updatePrice.executeUpdate();
}

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

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

另一个选项是
乐观锁定
。在将资源写回之前,会将其状态与最初读取时的状态进行比较。如果它们不同,则另一个进程更改了该资源,这通常会导致
OptimisticLockException
。好消息是,您可以捕获它并立即重试更新该资源。同样,您可以将冲突告知用户。这是你的选择

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


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

根据这个问题的答案,您可以尝试将@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);
    }
}

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

值得一提的是,您复杂而昂贵的查询只是
entityManager.find(Fruit.class,id)
。这些方法在服务或DAO(或存储库)中?@KimAragonEscobar,它在存储库类中。存储库类位于服务类内部