在spring中實現事務有多種方式,主要是兩種:一種是聲明式事務,一種是編程式事務,今天我們就講聲明式事務中的一種,使用注解@Transactional,這個注解的作用就是幫助我們在代碼執行完畢之后自動提交事務,在發生異常時進行回滾。
比如以下這段代碼:
@Transactional public void updateDBInfo(List<Entity> entityList){//待更新的數據entityList.stream().foreach((arg) -> {arg.setModifier("xxx");arg.setModifierTime("xxxx-xx-xx hh:mm:ss");});this.updateBatchById(entityList); }
?這是一段非常簡單的代碼段。假如我們就是需要加進行更新,但是如果這段代碼需要涉及到鎖的話,如果架構是分布式,也就是部署了多臺service Application服務器,然后進行負債均衡,那么此時如果有多個用戶同時操作的話,每個用戶的請求會由于負債均衡可能到達不同的service Application服務器應用。所以這時候我們的鎖用線程級別的鎖是不行的,只能用進程級別的鎖,也就是分布式鎖。分布式鎖的話可以用redis的那個命令叫setNx,這里我就不過多贅述了。主要講在事務里面有鎖會造成什么問題,以及如何解決該問題。
如果我們還是使用這個注解@Transactional,這個注解我們剛剛也說了。這個注解的作用就是幫助我們在代碼執行完畢之后自動提交事務,在發生異常時進行回滾。
首先改造代碼進行加鎖:
@Transactional public void updateDBInfo(List<Entity> entityList){ RLock lock = null; try {lock = redissonTool.tryLock(RedisKeyConstants.REDIS_KEY_PURCHASE_ORDER_EXTEND, Codes.KEY_ERROR);//待更新的數據entityList.stream().foreach((arg) -> {arg.setModifier("xxx");arg.setModifierTime("xxxx-xx-xx hh:mm:ss");});this.updateBatchById(entityList); } finally {redissonTool.unLock(lock); } }
?如果我們是這樣寫的話,那么會有什么問題呢?
當代碼執行完畢,也就是finally里面的鎖都釋放了,去提交事務,但是提交事務也是需要時間的,有可能還沒提交事務完,由于鎖釋放了,其他客戶端就會拿到鎖了,但是此時該用戶感知不到此時DB數據庫中的數據是最新的,也就是所謂的可見性,什么是可見性?
可見性:顧名思義就是一個線程的操作結果對其他所有線程都是可見的,因為每一個線程都共享同一個共享主內存。
那么這時候另一個用戶在客戶端就不知道這個DB的數據 是更改的,又會去更改,然后提交事務,這時候剛剛那個客戶端的用戶也提交事務成功了,那么就會造成臟數據了,數據不一致。
我給大家畫個圖展示:
但是由于我剛剛說的這種,A還沒提交更新事務之后,鎖釋放,B拿到,B更新完,提交更新事務,成功改為2,A的提交更新事務完成。此時就還是1.就是臟數據了。
所以有什么辦法解決呢?
那我們把自動事務注解@Transactional改成手動事務。讓事務提交成功之后再去釋放鎖,而不是在鎖釋放之后再提交事務。
手動事務我用的是transactionTemplate.execute(TransactionCallback callback);然后用的是TransactionCallback接口的抽象實現類,用匿名內部類。
TransactionCallbackWithoutResult的抽象方法:doInTransactionWithoutResult
也就是:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {//業務更新邏輯}});
總結:
? ? ? ? 在事務中碰到鎖,必須使用手動事務,讓事務提交成功之后,再去釋放鎖。而非釋放鎖之后再提交事務。也就是不能使用@Transactional注解的自動提交事務。
一般模板:
RLock rLock = null; try{rLock = ?redissonTool.tryLock("REDIS_LOCK_NAME"); }catch(){transactionTemplate.execute(new TransactionCallbackWithoutResult(){protected void doInTransctionWithoutResult(){//業務更新邏輯;}}) }finally{rlock.unlock(); }
?最后:
? ? ? ? 如果大家覺得這篇文章對你們有所幫助的話,麻煩點個免費的贊贊,謝謝,也祝各位碼農在未來的IT道路上越走越遠。