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

Spring Boot 動(dòng)態(tài)多數(shù)據(jù)源核心思路與關(guān)鍵介紹

 更新時(shí)間:2026年03月17日 10:16:27   作者:SimonKing  
這篇文章給大家介紹了Spring Boot 動(dòng)態(tài)多數(shù)據(jù)源核心思路與關(guān)鍵介紹,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧

01 引言

上一節(jié)我們使用Qoder完成了動(dòng)態(tài)數(shù)據(jù)源的demo,測試結(jié)果也沒有讓人失望。但是生成的代碼會(huì)給我們帶來什么樣的思考,如果是我們自己實(shí)現(xiàn),會(huì)不會(huì)考慮比Agent更加全面呢?

我們帶著閱讀、學(xué)習(xí)的態(tài)度了解多數(shù)據(jù)源的動(dòng)態(tài)切換的核心思路。

02 為什么需要?jiǎng)討B(tài)多數(shù)據(jù)源

業(yè)務(wù)場景驅(qū)動(dòng)技術(shù)選型:

  • 讀寫分離:主庫寫入,從庫讀取,提升并發(fā)能力
  • 數(shù)據(jù)隔離:不同業(yè)務(wù)模塊使用獨(dú)立數(shù)據(jù)庫
  • 分庫分表:業(yè)務(wù)增長后的必然選擇
  • 灰度發(fā)布:新舊數(shù)據(jù)庫并行,逐步遷移流量

核心訴求:在代碼無感知的情況下,根據(jù)業(yè)務(wù)需求自動(dòng)切換數(shù)據(jù)源。

主要的解決思路分大概兩種:

  • 繼承AbstractRoutingDataSource
  • 使用數(shù)據(jù)庫的schema

繼承AbstractRoutingDataSource 今天主要學(xué)習(xí)的技術(shù),簡單說一下數(shù)據(jù)庫的schema。

數(shù)據(jù)庫的schema的前提是一個(gè)賬號(hào)具有多個(gè)數(shù)據(jù)庫的讀寫權(quán)限,通過任意一個(gè)數(shù)據(jù)庫連接,來操作所有的數(shù)據(jù)庫。每一個(gè)SQL都必須通過tableName.表名的形式。

select * from test.user where id=1;
update test.user set name='AI' where id=1

03 實(shí)現(xiàn)思路

通過注解的方式,類似Mybaits-Plus的動(dòng)態(tài)數(shù)據(jù)源dynamic-datasource-spring-boot-starter的注解@DS

3.1 定義數(shù)據(jù)源注解

我們這里直接使用DataSource

/**
 * 多數(shù)據(jù)源切換注解
 * 用于在方法或類級(jí)別指定數(shù)據(jù)源
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /**
     * 數(shù)據(jù)源名稱
     * @return 數(shù)據(jù)源 key
     */
    String value() default "master";
}

關(guān)鍵點(diǎn):

  • 支持方法級(jí)和類級(jí),方法級(jí)優(yōu)先級(jí)更高
  • 默認(rèn)值避免遺忘配置
  • 運(yùn)行時(shí)保留供 AOP 識(shí)別

3.2 ThreadLocal 上下文管理

ThreadLocal是保持同一線程同一數(shù)據(jù)源的關(guān)鍵。

public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }
    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove(); // ?? 必須清理!
    }
}

關(guān)鍵點(diǎn):

  • ThreadLocal 保證線程隔離,無需加鎖
  • 每個(gè)請(qǐng)求線程獨(dú)立維護(hù)數(shù)據(jù)源上下文
  • 必須清理:防止線程池復(fù)用導(dǎo)致的數(shù)據(jù)污染

3.3 動(dòng)態(tài)路由數(shù)據(jù)源

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    /**
     * 決定當(dāng)前使用哪個(gè)數(shù)據(jù)源
     * @return 數(shù)據(jù)源 key
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceKey();
    }
}

AbstractRoutingDataSourcespring-jdbc留給我們的擴(kuò)展點(diǎn)。

Spring 的巧妙設(shè)計(jì)

  • 每次獲取數(shù)據(jù)庫連接前,自動(dòng)調(diào)用 determineCurrentLookupKey()
  • ThreadLocal 獲取當(dāng)前應(yīng)使用的數(shù)據(jù)源 key
  • 自動(dòng)從目標(biāo)數(shù)據(jù)源 Map 中查找并返回對(duì)應(yīng)數(shù)據(jù)源

3.4AOP自動(dòng)切換

使用注解自然少不了AOP的切面編程,我們需要通過@DataSource注解獲取運(yùn)行時(shí)數(shù)據(jù)源的信息。

@Aspect
@Component
@Order(1) // ?? 關(guān)鍵:必須小于事務(wù)切面的 Order
public class DataSourceAspect {
    @Pointcut("@annotation(DataSource) || @within(DataSource)")
    public void dataSourcePointcut() {}
    @Before("dataSourcePointcut()")
    public void before(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 優(yōu)先方法注解,其次類注解
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource == null) {
            dataSource = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
        }
        if (dataSource != null) {
            DataSourceContextHolder.setDataSourceKey(dataSource.value());
        }
    }
    @After("dataSourcePointcut()")
    public void after(JoinPoint joinPoint) {
        DataSourceContextHolder.clearDataSourceKey();
    }
}

執(zhí)行流程

  1. 方法執(zhí)行前 → 解析注解 → 設(shè)置數(shù)據(jù)源 key
  2. 執(zhí)行業(yè)務(wù)邏輯 → Spring 路由到對(duì)應(yīng)數(shù)據(jù)源
  3. 方法執(zhí)行后 → 清理ThreadLocal → 防止內(nèi)存泄漏

關(guān)鍵點(diǎn):

  • 優(yōu)先方法注解,其次類注解
  • 切換數(shù)據(jù)源的Order必須小于事務(wù)切面的Order,確保切換數(shù)據(jù)源在事務(wù)之前執(zhí)行。

04 配置綁定

上面是整個(gè)數(shù)據(jù)源動(dòng)態(tài)切換的整體思路,但是數(shù)據(jù)源怎么配置呢?

4.1 yml 配置

spring:
  application:
    name: boot-qoder
  datasource:
    # master 數(shù)據(jù)源配置
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/db_master?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: root
    # slave1 數(shù)據(jù)源配置
    slave1:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/db_slave1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: root
    # slave2 數(shù)據(jù)源配置
    slave2:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/db_slave2?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: root

4.2 配置類

@Configuration
@MapperScan(basePackages = "com.example.bootqoder.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
public class DataSourceConfig {
    /**
     * master 數(shù)據(jù)源
     * @return 數(shù)據(jù)源實(shí)例
     */
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create()
                .type(com.zaxxer.hikari.HikariDataSource.class)
                .build();
    }
    /**
     * slave1 數(shù)據(jù)源
     * @return 數(shù)據(jù)源實(shí)例
     */
    @Bean(name = "slave1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DataSourceBuilder.create()
                .type(com.zaxxer.hikari.HikariDataSource.class)
                .build();
    }
    /**
     * slave2 數(shù)據(jù)源
     * @return 數(shù)據(jù)源實(shí)例
     */
    @Bean(name = "slave2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DataSourceBuilder.create()
                .type(com.zaxxer.hikari.HikariDataSource.class)
                .build();
    }
    /**
     * 創(chuàng)建動(dòng)態(tài)路由數(shù)據(jù)源
     * @return 動(dòng)態(tài)數(shù)據(jù)源
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DataSource dynamicDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slave1DataSource") DataSource slave1DataSource,
            @Qualifier("slave2DataSource") DataSource slave2DataSource) {
        DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();
        // 配置多個(gè)數(shù)據(jù)源
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("slave1", slave1DataSource);
        targetDataSources.put("slave2", slave2DataSource);
        // 設(shè)置目標(biāo)數(shù)據(jù)源
        routingDataSource.setTargetDataSources(targetDataSources);
        // 設(shè)置默認(rèn)數(shù)據(jù)源
        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        return routingDataSource;
    }
    /**
     * 創(chuàng)建 SqlSessionFactory
     * @param dataSource 數(shù)據(jù)源
     * @return SqlSessionFactory
     * @throws Exception 異常
     */
    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        // 設(shè)置 Mapper XML 文件位置
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
        // 設(shè)置 MyBatis Plus 配置
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setMapUnderscoreToCamelCase(true);
        sessionFactory.setConfiguration(configuration);
        return sessionFactory.getObject();
    }
    /**
     * 創(chuàng)建 SqlSessionTemplate
     * @param sqlSessionFactory SqlSessionFactory
     * @return SqlSessionTemplate
     */
    @Bean(name = "sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

我們需要單獨(dú)每一個(gè)數(shù)據(jù)源都要交給Spring管理:

  • masterDataSource
  • slave1DataSource
  • slave2DataSource

然后才能創(chuàng)建動(dòng)態(tài)數(shù)據(jù)源,統(tǒng)一由dynamicDataSource管理,返回的數(shù)據(jù)源類型為com.example.bootqoder.datasource.DynamicRoutingDataSource,就是3.3創(chuàng)建的動(dòng)態(tài)路由數(shù)據(jù)源。

最后需要設(shè)置sqlSessionFactory,由于使用了Mybaits Plus。就需要將動(dòng)態(tài)數(shù)據(jù)源設(shè)置到com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean里面去。

05 小結(jié)

個(gè)人感覺AI生成的代碼,比我預(yù)想的要好多了。動(dòng)態(tài)切換數(shù)據(jù)源,我事先沒有考慮過在事務(wù)之后切換數(shù)據(jù)源的異常,但是AI考慮到了。

隨著AI編程的深入,后面可能我們都不太關(guān)注代碼該怎么寫了,從一個(gè)執(zhí)行者轉(zhuǎn)變成管理者,手搓代碼可能真的要變成大家調(diào)侃的古法編程了。

到此這篇關(guān)于Spring Boot 動(dòng)態(tài)多數(shù)據(jù)源核心思路與關(guān)鍵介紹的文章就介紹到這了,更多相關(guān)Spring Boot 動(dòng)態(tài)多數(shù)據(jù)源內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Java中的阻塞隊(duì)列

    詳解Java中的阻塞隊(duì)列

    在去年的面試過程中,被面試官問道“阻塞隊(duì)列”這個(gè)問題,因?yàn)楫?dāng)時(shí)并沒有對(duì)此問題進(jìn)行深入理解,只是按照自己的理解說明了該問題,最后面試結(jié)果也不太好,今天對(duì)該問題進(jìn)行簡要的面試并記錄如下;如有錯(cuò)誤,歡迎指正,需要的朋友可以參考下
    2021-06-06
  • SpringBoot監(jiān)控SQL運(yùn)行情況的流程步驟

    SpringBoot監(jiān)控SQL運(yùn)行情況的流程步驟

    Druid是Java語言中最好的數(shù)據(jù)庫連接池,雖然?HikariCP?的速度稍快,但是,Druid能夠提供強(qiáng)大的監(jiān)控和擴(kuò)展功能?,也是阿里巴巴的開源項(xiàng)目,本文給大家介紹了SpringBoot監(jiān)控SQL運(yùn)行情況的流程步驟,需要的朋友可以參考下
    2024-03-03
  • 關(guān)于SpringCloud?Ribbon替換輪詢算法問題

    關(guān)于SpringCloud?Ribbon替換輪詢算法問題

    Spring?Cloud?Ribbon是基于Netlix?Ribbon實(shí)現(xiàn)的一套客戶端負(fù)載均衡的工具。接下來通過本文給大家介紹SpringCloud?Ribbon替換輪詢算法問題,需要的朋友可以參考下
    2022-01-01
  • 如何將Java對(duì)象轉(zhuǎn)換成JSON

    如何將Java對(duì)象轉(zhuǎn)換成JSON

    這篇文章主要介紹了如何將Java對(duì)象轉(zhuǎn)換成JSON,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-11-11
  • java實(shí)現(xiàn)MD5加密方法匯總

    java實(shí)現(xiàn)MD5加密方法匯總

    本文給大家匯總介紹了2種java實(shí)現(xiàn)MD5加密的方法,非常的實(shí)用,這里分享給大家,學(xué)習(xí)下其中的思路,對(duì)大家學(xué)習(xí)java非常有幫助。
    2015-10-10
  • 工作中禁止使用Executors快捷創(chuàng)建線程池原理詳解

    工作中禁止使用Executors快捷創(chuàng)建線程池原理詳解

    這篇文章主要為大家介紹了工作中禁止使用Executors快捷創(chuàng)建線程池原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • 如何解決報(bào)錯(cuò):java.net.BindException:無法指定被請(qǐng)求的地址問題

    如何解決報(bào)錯(cuò):java.net.BindException:無法指定被請(qǐng)求的地址問題

    在Linux虛擬機(jī)上安裝并啟動(dòng)Tomcat時(shí)遇到啟動(dòng)失敗的問題,通過檢查端口及配置文件未發(fā)現(xiàn)異常,后發(fā)現(xiàn)/etc/hosts文件中缺少localhost的映射,添加后重啟Tomcat成功,Tomcat啟動(dòng)時(shí)會(huì)檢查localhost的IP映射,缺失或錯(cuò)誤都可能導(dǎo)致啟動(dòng)失敗
    2024-10-10
  • Java中兩種拋出異常的方式示例詳解

    Java中兩種拋出異常的方式示例詳解

    在Java中有兩種拋出異常的方式,一種是throw,直接拋出異常,另一種是throws,間接拋出異常,本文給大家詳細(xì)說明java中兩種拋出異常的方式,感興趣的朋友一起看看吧
    2023-08-08
  • java 遍歷MAP的幾種方法示例代碼

    java 遍歷MAP的幾種方法示例代碼

    本文主要介紹java 遍歷MAP的知識(shí)資料,這里整理幾種方法及實(shí)現(xiàn)示例代碼,有興趣的小伙伴可以參考下
    2016-09-09
  • springboot jpa實(shí)現(xiàn)優(yōu)雅處理isDelete的默認(rèn)值

    springboot jpa實(shí)現(xiàn)優(yōu)雅處理isDelete的默認(rèn)值

    如果多個(gè)實(shí)體類都有 isDelete 字段,并且你希望在插入時(shí)為它們統(tǒng)一設(shè)置默認(rèn)值時(shí)改怎么做呢,本文為大家整理了一些方法,希望對(duì)大家有所幫助
    2024-11-11

最新評(píng)論

平遥县| 牡丹江市| 平凉市| 呼图壁县| 阿巴嘎旗| 开原市| 灵石县| 五常市| 田阳县| 前郭尔| 定陶县| 甘孜县| 颍上县| 象州县| 宁国市| 崇仁县| 高清| 曲水县| 迁西县| 玉门市| 德江县| 昭平县| 隆化县| 渝北区| 都安| 二手房| 边坝县| 岑溪市| 台前县| 镶黄旗| 仁寿县| 葫芦岛市| 南阳市| 滦平县| 汉阴县| 梧州市| 巴塘县| 张家口市| 菏泽市| 忻城县| 民和|