我正在使用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);
}
这两种方法通常几乎同时被调用,因此会出现竞争条件。有没有办法解决这个问题?我认为将它们放在一个同步方法中并不是一个好主意,因为我期望不同用户同时调用这些方法(数千个),因此可能会导致延迟。如果我错了,请修理我。
答案 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);
}
}
我知道这篇文章的问题与你的不一样,但这可以解决你的问题。