2015-05-09 23 views
17

我在Spring和JPA/Hibernate中玩弄了一下,我對在表中增加計數器的方式感到困惑。Spring,JPA和Hibernate - 如何增加沒有併發問題的計數器

我的REST API需要增加,並根據用戶動作(在本例中波紋管,喜歡或不喜歡標籤將在標籤表使計數器遞增或遞減一個)

數據庫遞減某個值

tagRepository是JpaRepository(Spring的數據) 和我有這樣

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/> 

@Controller 
public class TestController { 

    @Autowired 
    TagService tagService 

    public void increaseTag() { 
     tagService.increaseTagcount(); 
    } 
    public void decreaseTag() { 
     tagService.decreaseTagcount(); 

    } 
} 

@Transactional 
@Service 
public class TagServiceImpl implements TagService { 


    pubic void decreaseTagcount() { 
     Tag tag = tagRepository.findOne(tagId); 
     decrement(tag) 
    } 

    pubic void increaseTagcount() { 
     Tag tag = tagRepository.findOne(tagId); 
     increment(tag) 
    } 

    private void increment(Tag tag) { 
     tag.setCount(tag.getCount() + 1); 
     Thread.sleep(20000); 
     tagRepository.save(tag); 
    } 

    private void decrement(Tag tag) { 
     tag.setCount(tag.getCount() - 1); 
     tagRepository.save(tag); 
    } 
} 

配置的交易正如你可以看到我已經穿上目的的增量20秒的睡眠.save前JUST()來能夠測試併發場景。

初始標籤計數器= 10;

1)用戶呼叫increaseTag和代碼擊中睡眠所以實體= 11和在DB中值的值 仍爲10

2)用戶呼叫decreaseTag並通過去所有的代碼。所述 值是數據庫現在= 9

3)休眠結束並且擊中用具有11 計數該實體的.save然後點擊.save()

當我檢查數據庫中,該標記的值現在等於11 ..實際上(至少是我想實現的)它將等於10

此行爲是否正常?或者@Transactional註解沒有做的是工作?

回答

34

最簡單的解決方法是delegate the concurrency to your database和單純依靠database isolation level鎖定在目前修改的行:

增量是像這樣簡單:

UPDATE Tag t set t.count = t.count + 1 WHERE t.id = :id; 

並且遞減查詢是:

UPDATE Tag t set t.count = t.count - 1 WHERE t.id = :id; 

UPDATE查詢對修改的行,防止其他事務修改同一行,before the current transaction commits(只要您不使用READ_UNCOMMITTED)。

+0

天才..這是處理悲觀主義者鎖定的100倍! – Johny19

+1

(順便提一下,我發佈了一些關於隔離級別的文檔,然後我剛剛意識到這是你的博客),它非常清楚並且很好地解釋了。所以+1也是如此! – Johny19

+0

感謝您欣賞我的作品。 –

1

例如,使用樂觀鎖定。 這應該是解決您的問題的最簡單的解決方案。 欲瞭解更多詳情,請參閱 - >https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html

+0

謝謝你的回答。所以,如果我使用樂觀鎖定,當檢測到競爭條件時會引發StaleObjectStateException。事情是,我不希望「增加標籤」失敗..我不想(我不認爲是非常推薦的)?趕上StaleObjectStateException並重試,直到.save沒有' t拋出異常?有沒有辦法用最新的DB值自動重試? – Johny19

+0

它不應該失敗,只是相應地處理異常。其他解決方案將是hacky(但我有一些如果你想要他們;)) –

+0

你的意思是在while循環中處理異常?因爲我需要重試incrementTag,直到我不再有異常。我無法逃脫這個例如 嘗試{增量)catch(StaleObjectStateException e)增量()} – Johny19