最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Boot 動態(tài)數(shù)據(jù)源在事務(wù)中切庫失效問題排查

 更新時(shí)間:2026年06月16日 08:17:55   作者:成都第一深情IZZO  
本文主要介紹了Spring Boot 動態(tài)數(shù)據(jù)源在事務(wù)中切庫失效問題排查,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

最近在項(xiàng)目里遇到一個(gè)比較典型的多數(shù)據(jù)源問題:代碼里明明給 Mapper 標(biāo)了 @DS("db2"),但是運(yùn)行時(shí) SQL 還是打到了主庫 db1,最終報(bào)錯(cuò):

Table 'vnfinance.invest_target_info' doesn't exist

或者某些配置查詢返回 null,導(dǎo)致后續(xù)空指針:

java.lang.NullPointerException
at InvestAssetServiceImpl.syncInvestAsset(...)

這個(gè)問題本質(zhì)不是表不存在,也不是數(shù)據(jù)丟了,而是事務(wù)中動態(tài)數(shù)據(jù)源切換失效。

背景

項(xiàng)目使用了動態(tài)數(shù)據(jù)源:

spring:
  datasource:
    dynamic:
      primary: db1
      datasource:
        db1:
          url: jdbc:mysql://.../vnfinance
        db2:
          url: jdbc:mysql://.../vncashloan

其中:

  • db1:投資業(yè)務(wù)庫,比如 invest_asset
  • db2:借款業(yè)務(wù)庫,比如 invest_target_infosys_config

Mapper 上也加了數(shù)據(jù)源注解:

@DS("db2")
public interface LoanerMapper {
    InvestAssetPOJO getInvest(@Param("investId") Long investId);
}
@DS("db2")
public interface SysConfigMapper extends BaseMapper<SysConfig> {
}

看起來應(yīng)該沒問題,但實(shí)際在某些調(diào)用鏈里還是查到了 db1。

問題代碼

原來的邏輯大概是這樣:

@Override
@Transactional(rollbackFor = Exception.class)
public InvestAsset syncInvestAsset(Long investId) {
    InvestAsset investAsset = baseMapper.getByInvestId(investId);

    if (investAsset == null || investAsset.getInvestId() == null) {
        InvestAssetPOJO invest = loanerMapper.getInvest(investId);

        SysConfig config = sysConfigService.getByConfigKey(INVEST_ASSET_TITLE_PREFIX);

        String titlePrefix = config.getConfigValue("??u t? linh ho?t: K? th? ");
        // ...
    }

    return investAsset;
}

這段代碼的問題在于:

baseMapper.getByInvestId(investId)

是查主庫 db1 的。

而整個(gè)方法又加了:

@Transactional(rollbackFor = Exception.class)

所以方法一開始進(jìn)入事務(wù)后,第一次數(shù)據(jù)庫訪問就拿到了 db1 的連接,并綁定到當(dāng)前線程。

后面再執(zhí)行:

loanerMapper.getInvest(investId)
sysConfigService.getByConfigKey(...)

雖然 Mapper 上標(biāo)了 @DS("db2"),但是當(dāng)前線程已經(jīng)在同一個(gè)事務(wù)里綁定了 db1 連接,動態(tài)數(shù)據(jù)源就切不過去了。

于是本來應(yīng)該查:

vncashloan.invest_target_info

結(jié)果查成了:

vnfinance.invest_target_info

最終報(bào)錯(cuò):

Table 'vnfinance.invest_target_info' doesn't exist

為什么之前沒問題

這個(gè)問題很容易讓人疑惑:之前數(shù)據(jù)一直正常,為什么現(xiàn)在突然出問題?

原因通常不是“之前真的沒問題”,而是之前沒有觸發(fā)這個(gè)隱患。

常見情況有幾種。

第一,之前查詢?nèi)肟跊]有事務(wù)。

比如列表頁直接調(diào)用:

loanerMapper.queryAllInvestAsset(...)

外面沒有先開啟 db1 事務(wù),@DS("db2") 可以正常生效。

第二,資產(chǎn)包已經(jīng)同步過。

如果 invest_asset 里已經(jīng)有數(shù)據(jù):

InvestAsset investAsset = baseMapper.getByInvestId(investId);

后面就不會進(jìn)入同步分支,也不會查 db2invest_target_infosys_config。

第三,舊代碼吞掉了異常。

有些 MQ 消費(fèi)代碼原來是這樣:

try {
    // 業(yè)務(wù)處理
} catch (Exception e) {
    log.error("change invest asset status fail:", e);
}

異常只打印,不繼續(xù)拋出。RocketMQ 可能認(rèn)為消費(fèi)成功,問題不會持續(xù)重試暴露出來。

后來改成異常繼續(xù)拋出后,MQ 會不斷重試,問題就變明顯了。

核心原因

一句話總結(jié):

動態(tài)數(shù)據(jù)源切換依賴當(dāng)前線程上下文,但 Spring 事務(wù)一旦開啟并拿到連接,連接會綁定到當(dāng)前線程。后續(xù)同一個(gè)事務(wù)里再切換數(shù)據(jù)源,可能不會生效。

尤其是這種代碼非常危險(xiǎn):

@Transactional
public void business() {
    db1Mapper.selectById(id);   // 先綁定 db1

    db2Mapper.selectById(id);   // 期望切 db2,但可能仍然走 db1
}

Mapper 上的 @DS("db2") 并不能保證在已經(jīng)存在事務(wù)連接的情況下強(qiáng)制切庫。

解決辦法

解決思路是:跨庫查詢不要直接混在同一個(gè)事務(wù)里,要把 db2 查詢拆到獨(dú)立 Service,并開啟新的只讀事務(wù)。

1. 新增 db2 查詢 Service

@Service
@DS("db2")
public class LoanerDb2Service {

    private final LoanerMapper loanerMapper;

    public LoanerDb2Service(LoanerMapper loanerMapper) {
        this.loanerMapper = loanerMapper;
    }

    /**
     * 從借款庫查詢資產(chǎn)包基礎(chǔ)信息。
     * 外層可能已有 db1 事務(wù),這里單獨(dú)開啟 db2 只讀事務(wù),避免查到主庫。
     */
    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
    public InvestAssetPOJO getInvest(Long investId) {
        return loanerMapper.getInvest(investId);
    }

    /**
     * 從借款庫查詢資產(chǎn)包關(guān)聯(lián)的借款人信息。
     */
    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
    public List<InvestLoanerPOJO> findByInvestIdFromLoan(Long investId) {
        return loanerMapper.findByInvestIdFromLoan(investId);
    }
}

重點(diǎn)是這兩個(gè)注解:

@DS("db2")
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)

REQUIRES_NEW 會掛起外層事務(wù),重新開啟一個(gè)新的事務(wù),從而重新獲取 db2 連接。

2. SysConfigService 也要處理

配置表如果也在 db2,同樣不能只依賴 Mapper 上的 @DS("db2")。

@Service
@DS("db2")
public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig>
        implements SysConfigService {

    private final SysConfigMapper sysConfigMapper;

    public SysConfigServiceImpl(SysConfigMapper sysConfigMapper) {
        this.sysConfigMapper = sysConfigMapper;
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
    public SysConfig getByConfigKey(String configKey) {
        LambdaQueryWrapper<SysConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysConfig::getConfigKey, configKey);
        return sysConfigMapper.selectOne(wrapper);
    }
}

3. 業(yè)務(wù) Service 不再直接調(diào) db2 Mapper

原來:

InvestAssetPOJO invest = loanerMapper.getInvest(investId);

改成:

InvestAssetPOJO invest = loanerDb2Service.getInvest(investId);

原來:

List<InvestLoanerPOJO> loaners = loanerMapper.findByInvestIdFromLoan(investId);

改成:

List<InvestLoanerPOJO> loaners = loanerDb2Service.findByInvestIdFromLoan(investId);

這樣即使當(dāng)前方法在 db1 事務(wù)中,db2 查詢也會進(jìn)入獨(dú)立事務(wù),不會被外層連接污染。

順手補(bǔ)充:配置讀取要兜底

這次還暴露了另一個(gè)問題:配置缺失時(shí)直接空指針。

原代碼:

SysConfig config = sysConfigService.getByConfigKey(INVEST_ASSET_TITLE_PREFIX);
String value = config.getConfigValue("??u t? linh ho?t: K? th? ");

如果 confignull,直接 NPE。

建議封裝:

private String getConfigValue(String configKey, String defaultValue) {
    SysConfig config = sysConfigService.getByConfigKey(configKey);
    return config == null ? defaultValue : config.getConfigValue(defaultValue);
}

使用:

String titlePrefix = getConfigValue(
    INVEST_ASSET_TITLE_PREFIX,
    "??u t? linh ho?t: K? th? "
);

這樣即使配置缺失,也不會把 MQ 消費(fèi)卡死。

最佳實(shí)踐總結(jié)

多數(shù)據(jù)源項(xiàng)目里,建議遵守幾個(gè)原則:

  1. 不要在一個(gè)事務(wù)里混查多個(gè)庫。
  2. @DS 盡量加在 Service 方法上,而不是只加 Mapper。
  3. 跨庫查詢拆成獨(dú)立 Service。
  4. 外層已有事務(wù)時(shí),跨庫查詢使用 Propagation.REQUIRES_NEW。
  5. 只讀查詢加 readOnly = true。
  6. MQ 消費(fèi)不要吞異常,否則業(yè)務(wù)失敗但消息被確認(rèn),會留下臟數(shù)據(jù)。
  7. 配置類數(shù)據(jù)讀取要有默認(rèn)值兜底,避免配置缺失導(dǎo)致核心流程失敗。

結(jié)論

這個(gè)問題看起來像是“表不存在”或者“配置為空”,但真正原因是:

外層 db1 事務(wù)已經(jīng)綁定連接,導(dǎo)致后續(xù) db2 Mapper 的動態(tài)數(shù)據(jù)源切換沒有生效。

解決方式不是簡單給 Mapper 加 @DS("db2"),而是把 db2 查詢下沉到獨(dú)立 Service,并使用:

@DS("db2")
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)

這樣才能保證在復(fù)雜事務(wù)鏈路、MQ 消費(fèi)、定時(shí)任務(wù)中,數(shù)據(jù)源切換穩(wěn)定可靠。

到此這篇關(guān)于Spring Boot 動態(tài)數(shù)據(jù)源在事務(wù)中切庫失效問題排查的文章就介紹到這了,更多相關(guān)Spring Boot 動態(tài)數(shù)據(jù)源切庫失效內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于Spring定時(shí)任務(wù)的fixedRate和fixedDelay的區(qū)別

    基于Spring定時(shí)任務(wù)的fixedRate和fixedDelay的區(qū)別

    這篇文章主要介紹了基于Spring定時(shí)任務(wù)的fixedRate和fixedDelay的區(qū)別,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Java自動化實(shí)現(xiàn)將HTML轉(zhuǎn)換為Word

    Java自動化實(shí)現(xiàn)將HTML轉(zhuǎn)換為Word

    在日常的軟件開發(fā)和企業(yè)應(yīng)用中,我們經(jīng)常會遇到需要將動態(tài)生成的 HTML 內(nèi)容轉(zhuǎn)化為規(guī)范的 Word 文檔,本文將介紹如何利用 Java 輕松實(shí)現(xiàn) HTML 到 Word 的高效轉(zhuǎn)換,需要的可以了解下
    2025-10-10
  • Java如何判斷一個(gè)IP是否在給定的網(wǎng)段內(nèi)

    Java如何判斷一個(gè)IP是否在給定的網(wǎng)段內(nèi)

    這篇文章主要介紹了Java如何判斷一個(gè)IP是否在給定的網(wǎng)段內(nèi)問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Java多線Condition條件變量正確使用方法詳解

    Java多線Condition條件變量正確使用方法詳解

    這篇文章主要為大家,介紹了Java多線Condition條件變量正確使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 關(guān)于兩個(gè)BeanUtils.copyProperties()的用法及區(qū)別

    關(guān)于兩個(gè)BeanUtils.copyProperties()的用法及區(qū)別

    這篇文章主要介紹了關(guān)于兩個(gè)BeanUtils.copyProperties()的用法及區(qū)別說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • java實(shí)現(xiàn)的DES加密算法詳解

    java實(shí)現(xiàn)的DES加密算法詳解

    這篇文章主要介紹了java實(shí)現(xiàn)的DES加密算法,結(jié)合實(shí)例形式詳細(xì)分析了java實(shí)現(xiàn)DES加密操作的原理、實(shí)現(xiàn)技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2017-06-06
  • Spring依賴注入多種類型數(shù)據(jù)的示例代碼

    Spring依賴注入多種類型數(shù)據(jù)的示例代碼

    這篇文章主要介紹了Spring依賴注入多種類型數(shù)據(jù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • Eureka源碼核心類預(yù)備知識

    Eureka源碼核心類預(yù)備知識

    這篇文章主要為大家介紹了Eureka源碼核心類預(yù)備知識詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • idea 無法創(chuàng)建Scala class 選項(xiàng)的原因分析及解決辦法匯總

    idea 無法創(chuàng)建Scala class 選項(xiàng)的原因分析及解決辦法匯總

    這篇文章主要介紹了idea 無法創(chuàng)建Scala class 選項(xiàng)的解決辦法匯總,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • JAVA和JAVAC 命令詳細(xì)介紹

    JAVA和JAVAC 命令詳細(xì)介紹

    這篇文章主要介紹了JAVA和JAVAC 命令詳細(xì)介紹的相關(guān)資料,本文對java 和javac 命令進(jìn)行了詳解,并在Windows和Linux 平臺分別說明,需要的朋友可以參考下
    2016-11-11

最新評論

当涂县| 花莲市| 石狮市| 璧山县| 孝感市| 晋城| 墨竹工卡县| 东阳市| 晋宁县| 喀喇沁旗| 德昌县| 黑水县| 三都| 江北区| 霍州市| 安岳县| 思南县| 高阳县| 班戈县| 湛江市| 汨罗市| 清水县| 海宁市| 永川市| 大新县| 华阴市| 监利县| 安多县| 永州市| 汉寿县| 河源市| 南丰县| 盈江县| 革吉县| 额敏县| 宁海县| 开原市| 阳原县| 太仆寺旗| 隆化县| 富平县|