亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

當(dāng)Transactional遇上synchronized的解決方法分享

 更新時(shí)間:2023年05月07日 14:08:17   作者:思想領(lǐng)袖  
前些時(shí)間剛好刷到了有關(guān)于“# 【事務(wù)與鎖】當(dāng)Transactional遇上synchronized”這一類的文章,感覺這也是工作中經(jīng)常會(huì)遇到的一類問題了。所以就針對這個(gè)話題進(jìn)行了分析并整理了常用的解決方法,希望對大家有所幫助

問題情形

假設(shè)代碼如下:

//controller層:
@GetMapping("/t1")
@Transactional(rollbackFor = Exception.class)
public void getTest1() {
    String n = countNumService.getCount();
    System.out.println(" t1 : " + n);
    try {
        Thread.sleep(60000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

@GetMapping("/t2")
@Transactional(rollbackFor = Exception.class)
public void getTest2() {
    String n = countNumService.getCount();
    System.out.println(" t2 : " + n);
    // 忽略其他的增刪操作
}

//service層:
@Override
@Transactional(rollbackFor = Exception.class)
public synchronized String getCount() {
    // 獲取
    CountNum countNum = countNumMapper.selectById(1);

    countNum.setNumber(countNum.getNumber() + 1);
    // 修改
    countNumMapper.updateById(countNum);

    return countNum.getNumber().toString();
}

問題分析

首先,在 getTest1()getTest2() 這兩個(gè)方法中都加了 @Transactional 注解,因此它們會(huì)分別開啟自己的事務(wù)。假設(shè)在某一刻,兩個(gè)線程同時(shí)調(diào)用了 /t1/t2 接口,并且 /t1 接口中執(zhí)行了較長的睡眠操作,于是 /t2 的業(yè)務(wù)邏輯率先完成,并且更新了數(shù)據(jù)庫中的Number字段。

隨后 /t1 的業(yè)務(wù)邏輯也完成了,但是由于之前被阻塞了 60 秒鐘,此時(shí)讀取到的計(jì)數(shù)器值已經(jīng)過期了,不能反映最新的狀態(tài)。因此 /t1 返回的結(jié)果將是過期的數(shù)據(jù),與 /t2 返回的結(jié)果不一致。所以這段代碼存在一個(gè)并發(fā)問題,可能導(dǎo)致數(shù)據(jù)的不一致性。

解決方法

這里我給出常用的解決方法:

  • getCount() 方法上的 synchronized 去掉,使用樂觀鎖的方式來控制并發(fā)訪問。
  • /t1/t2 兩個(gè)接口的事務(wù)設(shè)置為同一個(gè)事務(wù),即兩個(gè)接口共享同一個(gè)事務(wù)上下文??梢酝ㄟ^ Spring 的聲明式事務(wù)管理機(jī)制來實(shí)現(xiàn)。(在 Spring 框架中,我們可以使用 @Transactional 注解來聲明式地管理事務(wù)。使用 PROPAGATION_REQUIRED 屬性可以表示當(dāng)前方法需要加入到一個(gè)存在的事務(wù)中,如果不存在,則開啟新的事務(wù)。)
  • getCount() 方法中,進(jìn)行增量更新,而不是直接把Number字段加 1,例如使用 update count_num set number = number + 1 where id = ? 等 SQL 語句來實(shí)現(xiàn)。
  • 保留synchronized關(guān)鍵字的情況下,添加事務(wù)管理器進(jìn)行手動(dòng)事務(wù)管理。

代碼參考

1.樂觀鎖方案

@Override
@Transactional(rollbackFor = Exception.class)
public String getCount() {
    CountNum countNum = countNumMapper.selectById(1);
    // 使用版本號(hào)作為樂觀鎖
    int version = countNum.getVersion();
    countNum.setNumber(countNum.getNumber() + 1);
    countNum.setVersion(version + 1);
    // 更新操作必須要包含版本號(hào)字段
    int rows = countNumMapper.updateById(countNum);
    if (rows == 0) {
        throw new OptimisticLockException("事務(wù)中更新失敗");
    }
    return countNum.getNumber().toString();
}

2.將 /t1 和 /t2 兩個(gè)接口的事務(wù)設(shè)置為同一個(gè)事務(wù)。在 CountNumService 類的事務(wù)注解上添加了 propagation = Propagation.REQUIRED 屬性,表示當(dāng)前方法需要加入到一個(gè)已存在的事務(wù)中。此時(shí),如果 /t1/t2 調(diào)用的是同一個(gè) CountNumService 實(shí)例,則它們將共享同一個(gè)事務(wù)上下文。

@Service
public class CountNumService {
    @Autowired
    private CountNumMapper countNumMapper;
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public String getCount() {
         // 與之前代碼相同
    }
}
@RestController
public class CountNumController {
    @Autowired
    private CountNumService countNumService;
    @GetMapping("/t1")
    public void getTest1() {
        String n = countNumService.getCount();
        System.out.println(" t1 : " + n);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    @GetMapping("/t2")
    public void getTest2() {
        String n = countNumService.getCount();
        System.out.println(" t2 : " + n);
        // 忽略其他的增刪操作
    }
}

3.SQL中增量更新方案

@Mapper
public interface CountNumMapper {
    @Update("UPDATE count_num SET number = number + 1 WHERE id = 1")
    int updateNumber();
}
@Override
@Transactional(rollbackFor = Exception.class)
public String getCount() {
    // 直接執(zhí)行 SQL 語句進(jìn)行增量更新操作
    countNumMapper.updateNumber();
    // 再查詢一次獲取最新值
    CountNum countNum = countNumMapper.selectById(1);
    return countNum.getNumber().toString();
}

4.手動(dòng)事務(wù)管理方案

@Service
public class CountNumService {
    @Autowired
    private CountNumMapper countNumMapper;

    // 使用spring事務(wù)管理器
    @Autowired
    private PlatformTransactionManager transactionManager;

    public synchronized String getCount() {
        TransactionStatus status = null;
        try {
            DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
            //將傳播行為設(shè)置為 PROPAGATION_REQUIRED,以確保當(dāng)前方法在事務(wù)內(nèi)執(zhí)行:
            definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            //獲取當(dāng)前事務(wù)的狀態(tài)信息:
            status = transactionManager.getTransaction(definition);
            
            CountNum countNum = countNumMapper.selectById(1);
            countNum.setNumber(countNum.getNumber() + 1);

            int rows = countNumMapper.updateById(countNum);
            if (rows == 0) {
                throw new RuntimeException("事務(wù)中更新失敗");
            }
            //提交事務(wù):
            transactionManager.commit(status);
            return countNum.getNumber().toString();
        } catch (Exception e) {
            if (status != null) {
                //回滾事務(wù):
                transactionManager.rollback(status);
            }
            throw e;
        }
    }
}

到此這篇關(guān)于當(dāng)Transactional遇上synchronized的解決方法分享的文章就介紹到這了,更多相關(guān)Transactional synchronized內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何基于JWT實(shí)現(xiàn)接口的授權(quán)訪問詳解

    如何基于JWT實(shí)現(xiàn)接口的授權(quán)訪問詳解

    授權(quán)是最常見的JWT使用場景,下面這篇文章主要給大家介紹了關(guān)于如何基于JWT實(shí)現(xiàn)接口的授權(quán)訪問的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • Groovy的規(guī)則腳本引擎實(shí)例解讀

    Groovy的規(guī)則腳本引擎實(shí)例解讀

    這篇文章主要介紹了Groovy的規(guī)則腳本引擎實(shí)例解讀,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • 應(yīng)用Java泛型和反射導(dǎo)出CSV文件的方法

    應(yīng)用Java泛型和反射導(dǎo)出CSV文件的方法

    這篇文章主要介紹了應(yīng)用Java泛型和反射導(dǎo)出CSV文件的方法,通過一個(gè)自定義函數(shù)結(jié)合泛型與反射的應(yīng)用實(shí)現(xiàn)導(dǎo)出CSV文件的功能,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2014-12-12
  • SpringBoot 2.x 整合Lombok的方法示例

    SpringBoot 2.x 整合Lombok的方法示例

    Spring Boot是非常高效的開發(fā)框架,lombok是一套代碼模板解決方案,將極大提升開發(fā)的效率,這篇文章主要介紹了SpringBoot 2.x 整合Lombok的方法示例,感興趣的小伙伴們可以參考一下
    2018-06-06
  • mybatis-plus開啟sql日志打印的三種方法

    mybatis-plus開啟sql日志打印的三種方法

    本文主要介紹了mybatis-plus開啟sql日志打印的三種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • Spring中自動(dòng)注入的兩種方式總結(jié)

    Spring中自動(dòng)注入的兩種方式總結(jié)

    Spring的核心技術(shù)IOC(Intorol of Converse控制反轉(zhuǎn))的實(shí)現(xiàn)途徑是DI(dependency Insert依賴注入)。而依賴注入(DI)的實(shí)現(xiàn)方式又有兩種,xml方式和注解方式。本文就來詳細(xì)聊聊這兩個(gè)方式,需要的可以了解一下
    2022-10-10
  • Java 利用枚舉實(shí)現(xiàn)接口進(jìn)行統(tǒng)一管理

    Java 利用枚舉實(shí)現(xiàn)接口進(jìn)行統(tǒng)一管理

    這篇文章主要介紹了Java 利用枚舉實(shí)現(xiàn)接口進(jìn)行統(tǒng)一管理,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • 基于Spring Batch 配置重試邏輯

    基于Spring Batch 配置重試邏輯

    這篇文章主要介紹了Spring Batch 配置重試邏輯,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 基于SpringBoot接口+Redis解決用戶重復(fù)提交問題

    基于SpringBoot接口+Redis解決用戶重復(fù)提交問題

    當(dāng)網(wǎng)絡(luò)延遲的情況下用戶多次點(diǎn)擊submit按鈕導(dǎo)致表單重復(fù)提交,用戶提交表單后,點(diǎn)擊瀏覽器的【后退】按鈕回退到表單頁面后進(jìn)行再次提交也會(huì)出現(xiàn)用戶重復(fù)提交,辦法有很多,我這里只說一種,利用Redis的set方法搞定,需要的朋友可以參考下
    2023-10-10
  • Java讀取本地json文件及相應(yīng)處理方法

    Java讀取本地json文件及相應(yīng)處理方法

    今天小編就為大家分享一篇Java讀取本地json文件及相應(yīng)處理方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09

最新評論