2016-07-13 147 views
2

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

這兩種方法往往是在同一時間差點叫,正因爲這樣的競爭條件發生。有沒有辦法解決這個問題?我認爲把它們放在一個同步的方法中並不是一個好主意,因爲我期望不同用戶同時調用這些方法(數千個)很多,所以會導致延遲。修復我,如果我錯了。

+0

值得一提的是,您複雜而昂貴的查詢只是'entityManager.find(Fruit.class,id)'。 – chrylis

+0

這種方法是在服務中還是在DAO(或存儲庫)中? –

+0

@KimAragonEscobar,它位於存儲庫類中。存儲庫類在服務類中 –

回答

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

處理競賽條件的典型方法是locks。在pessimistic方案中,如果另一個事務當前處於活動狀態,您將禁止數據庫接受資源上的任何事務。

另一種選擇是optimistic locking。在寫回資源之前,將其狀態與初始讀取時的狀態進行比較。如果它們不同,另一個過程已更改該資源,通常以OptimisticLockException結束。好處是,您可能會抓住它並立即重新更新該資源。就像你可以告訴用戶有關衝突一樣。這是你的選擇。

這兩種解決方案都適合您的使用案例。選擇哪一個取決於許多因素。我會建議你閱讀鎖,然後自己選擇。

您可能還想考慮是否有必要立即將資源提交給數據庫。如果你希望它們在接下來的第二秒內被修改,你可以將它們存儲在內存中並每隔n秒刷新一次,這可以爲你節省一些數據庫開銷。在大多數情況下,這個提議可能是一個壞主意。這只是一個沒有更深入洞察你的應用程序的想法。

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

我知道,從這個職位的問題不在於你有相同的,但是這可能會解決你的問題。