Java 事务之间的竞争条件
我正在使用Spring MVC开发webapp,在我的应用程序中有这样的方法: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");
@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,它在存储库类中。存储库类位于服务类内部