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

SpringBoot+ThreadLocal+AbstractRoutingDataSource實現動態(tài)切換數據源

 更新時間:2023年08月08日 11:11:31   作者:搶老婆酸奶的小肥仔  
最近在做業(yè)務需求時,需要從不同的數據庫中獲取數據然后寫入到當前數據庫中,因此涉及到切換數據源問題,所以本文采用ThreadLocal+AbstractRoutingDataSource來模擬實現dynamic-datasource-spring-boot-starter中線程數據源切換,需要的朋友可以參考下

最近在做業(yè)務需求時,需要從不同的數據庫中獲取數據然后寫入到當前數據庫中,因此涉及到切換數據源問題。本來想著使用Mybatis-plus中提供的動態(tài)數據源SpringBoot的starter:dynamic-datasource-spring-boot-starter來實現。結果引入后發(fā)現由于之前項目環(huán)境問題導致無法使用。然后研究了下數據源切換代碼,決定自己采用ThreadLocal+AbstractRoutingDataSource來模擬實現dynamic-datasource-spring-boot-starter中線程數據源切換。

1、簡介

上述提到了ThreadLocal和AbstractRoutingDataSource,我們來對其進行簡單介紹下。

ThreadLocal :想必大家必不會陌生,全稱:thread local variable。主要是為解決多線程時由于并發(fā)而產生數據不一致問題。ThreadLocal為每個線程提供變量副本,確保每個線程在某一時間訪問到的不是同一個對象,這樣做到了隔離性,增加了內存,但大大減少了線程同步時的性能消耗,減少了線程并發(fā)控制的復雜程度。

ThreadLocal作用:在一個線程中共享,不同線程間隔離

ThreadLocal原理:ThreadLocal存入值時,會獲取當前線程實例作為key,存入當前線程對象中的Map中。

AbstractRoutingDataSource :根據用戶定義的規(guī)則選擇當前的數據源,

作用:在執(zhí)行查詢之前,設置使用的數據源,實現動態(tài)路由的數據源,在每次數據庫查詢操作前執(zhí)行它的抽象方法determineCurrentLookupKey(),決定使用哪個數據源。

2、代碼實現

程序環(huán)境:

SpringBoot2.4.8

Mybatis-plus3.2.0

Druid1.2.6

lombok1.18.20

commons-lang3 3.10

2.1 實現ThreadLocal

創(chuàng)建一個類用于實現ThreadLocal,主要是通過get,set,remove方法來獲取、設置、刪除當前線程對應的數據源。

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 11:21
 **/
public class DataSourceContextHolder {
    //此類提供線程局部變量。這些變量不同于它們的正常對應關系是每個線程訪問一個線程(通過get、set方法),有自己的獨立初始化變量的副本。
    private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
    /**
     * 設置數據源
     * @param dataSourceName 數據源名稱
     */
    public static void setDataSource(String dataSourceName){
        DATASOURCE_HOLDER.set(dataSourceName);
    }
    /**
     * 獲取當前線程的數據源
     * @return 數據源名稱
     */
    public static String getDataSource(){
        return DATASOURCE_HOLDER.get();
    }
    /**
     * 刪除當前數據源
     */
    public static void removeDataSource(){
        DATASOURCE_HOLDER.remove();
    }
}

2.2 實現AbstractRoutingDataSource

定義一個動態(tài)數據源類實現AbstractRoutingDataSource,通過determineCurrentLookupKey方法與上述實現的ThreadLocal類中的get方法進行關聯,實現動態(tài)切換數據源。

/**
 * @author: jiangjs
 * @description: 實現動態(tài)數據源,根據AbstractRoutingDataSource路由到不同數據源中
 * @date: 2023/7/27 11:18
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

上述代碼中,還實現了一個動態(tài)數據源類的構造方法,主要是為了設置默認數據源,以及以Map保存的各種目標數據源。其中Map的key是設置的數據源名稱,value則是對應的數據源(DataSource)。

2.3 配置數據庫

application.yml中配置數據庫信息:

#設置數據源
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      master:
        url: jdbc:mysql://xxxxxx:3306/test1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      slave:
        url: jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 15
      min-idle: 15
      max-active: 200
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: ""
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: false
      connection-properties: false
/**
 * @author: jiangjs
 * @description: 設置數據源
 * @date: 2023/7/27 11:34
 **/
@Configuration
public class DateSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(){
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource(){
        return DruidDataSourceBuilder.create().build();
    }
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource createDynamicDataSource(){
        Map<Object,Object> dataSourceMap = new HashMap<>();
        DataSource defaultDataSource = masterDataSource();
        dataSourceMap.put("master",defaultDataSource);
        dataSourceMap.put("slave",slaveDataSource());
        return new DynamicDataSource(defaultDataSource,dataSourceMap);
    }
}

通過配置類,將配置文件中的配置的數據庫信息轉換成datasource,并添加到DynamicDataSource中,同時通過@Bean將DynamicDataSource注入Spring中進行管理,后期在進行動態(tài)數據源添加時,會用到。

2.4 測試

在主從兩個測試庫中,分別添加一張表test_user,里面只有一個字段user_name。

create table test_user(
  user_name varchar(255) not null comment '用戶名'
)

在主庫添加信息:

insert into test_user (user_name) value ('master');

從庫中添加信息:

insert into test_user (user_name) value ('slave'); 

我們創(chuàng)建一個getData的方法,參數就是需要查詢數據的數據源名稱。

@GetMapping("/getData.do/{datasourceName}")
public String getMasterData(@PathVariable("datasourceName") String datasourceName){
    DataSourceContextHolder.setDataSource(datasourceName);
    TestUser testUser = testUserMapper.selectOne(null);
    DataSourceContextHolder.removeDataSource();
    return testUser.getUserName();
}

其他的Mapper和實體類大家自行實現。

執(zhí)行結果:

1、傳遞master時:

2、傳遞slave時:

通過執(zhí)行結果,我們看到傳遞不同的數據源名稱,查詢對應的數據庫是不一樣的,返回結果也不一樣。

在上述代碼中,我們看到 DataSourceContextHolder.setDataSource(datasourceName); 來設置了當前線程需要查詢的數據庫,通過 DataSourceContextHolder.removeDataSource(); 來移除當前線程已設置的數據源。使用過Mybatis-plus動態(tài)數據源的小伙伴,應該還記得我們在使用切換數據源時會使用到 DynamicDataSourceContextHolder.push(String ds); DynamicDataSourceContextHolder.poll(); 這兩個方法,翻看源碼我們會發(fā)現其實就是在使用ThreadLocal時使用了棧,這樣的好處就是能使用多數據源嵌套,這里就不帶大家實現了,有興趣的小伙伴可以看看Mybatis-plus中動態(tài)數據源的源碼。

注:啟動程序時,小伙伴不要忘記將SpringBoot自動添加數據源進行排除哦,否則會報循環(huán)依賴問題。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

2.5 優(yōu)化調整

2.5.1 注解切換數據源

在上述中,雖然已經實現了動態(tài)切換數據源,但是我們會發(fā)現如果涉及到多個業(yè)務進行切換數據源的話,我們就需要在每一個實現類中添加這一段代碼。

說到這有小伙伴應該就會想到使用注解來進行優(yōu)化,接下來我們來實現一下。

2.5.1.1 定義注解

我們就用mybatis動態(tài)數據源切換的注解:DS,代碼如下:

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 14:39
 **/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DS {
    String value() default "master";
}

2.5.1.2 實現aop

@Aspect
@Component
@Slf4j
public class DSAspect {
    @Pointcut("@annotation(com.jiashn.dynamic_datasource.dynamic.aop.DS)")
    public void dynamicDataSource(){}
    @Around("dynamicDataSource()")
    public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature)point.getSignature();
        Method method = signature.getMethod();
        DS ds = method.getAnnotation(DS.class);
        if (Objects.nonNull(ds)){
            DataSourceContextHolder.setDataSource(ds.value());
        }
        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.removeDataSource();
        }
    }
}

代碼使用了@Around,通過ProceedingJoinPoint獲取注解信息,拿到注解傳遞值,然后設置當前線程的數據源。對aop不了解的小伙伴可以自行google或百度。

2.5.1.3 測試

添加兩個測試方法:

@GetMapping("/getMasterData.do")
public String getMasterData(){
    TestUser testUser = testUserMapper.selectOne(null);
    return testUser.getUserName();
}
@GetMapping("/getSlaveData.do")
@DS("slave")
public String getSlaveData(){
    TestUser testUser = testUserMapper.selectOne(null);
    return testUser.getUserName();
}

由于@DS中設置的默認值是:master,因此在調用主數據源時,可以不用進行添加。

執(zhí)行結果:

1、調用getMasterData.do方法:

2、調用getSlaveData.do方法:

通過執(zhí)行結果,我們通過@DS也進行了數據源的切換,實現了Mybatis-plus動態(tài)切換數據源中的通過注解切換數據源的方式。

2.5.2 動態(tài)添加數據源

業(yè)務場景:有時候我們的業(yè)務會要求我們從保存有其他數據源的數據庫表中添加這些數據源,然后再根據不同的情況切換這些數據源。

因此我們需要改造下DynamicDataSource來實現動態(tài)加載數據源。

2.5.2.1 數據源實體

/**
 * @author: jiangjs
 * @description: 數據源實體
 * @date: 2023/7/27 15:55
 **/
@Data
@Accessors(chain = true)
public class DataSourceEntity {
    /**
     * 數據庫地址
     */
    private String url;
    /**
     * 數據庫用戶名
     */
    private String userName;
    /**
     * 密碼
     */
    private String passWord;
    /**
     * 數據庫驅動
     */
    private String driverClassName;
    /**
     * 數據庫key,即保存Map中的key
     */
    private String key;
}

實體中定義數據源的一般信息,同時定義一個key用于作為DynamicDataSource中Map中的key。

2.5.2.2 修改DynamicDataSource

/**
 * @author: jiangjs
 * @description: 實現動態(tài)數據源,根據AbstractRoutingDataSource路由到不同數據源中
 * @date: 2023/7/27 11:18
 **/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
    private final Map<Object,Object> targetDataSourceMap;
    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
        this.targetDataSourceMap = targetDataSources;
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
    /**
     * 添加數據源信息
     * @param dataSources 數據源實體集合
     * @return 返回添加結果
     */
    public void createDataSource(List<DataSourceEntity> dataSources){
        try {
            if (CollectionUtils.isNotEmpty(dataSources)){
                for (DataSourceEntity ds : dataSources) {
                    //校驗數據庫是否可以連接
                    Class.forName(ds.getDriverClassName());
                    DriverManager.getConnection(ds.getUrl(),ds.getUserName(),ds.getPassWord());
                    //定義數據源
                    DruidDataSource dataSource = new DruidDataSource();
                    BeanUtils.copyProperties(ds,dataSource);
                    //申請連接時執(zhí)行validationQuery檢測連接是否有效,這里建議配置為TRUE,防止取到的連接不可用
                    dataSource.setTestOnBorrow(true);
                    //建議配置為true,不影響性能,并且保證安全性。
                    //申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。
                    dataSource.setTestWhileIdle(true);
                    //用來檢測連接是否有效的sql,要求是一個查詢語句。
                    dataSource.setValidationQuery("select 1 ");
                    dataSource.init();
                    this.targetDataSourceMap.put(ds.getKey(),dataSource);
                }
                super.setTargetDataSources(this.targetDataSourceMap);
                // 將TargetDataSources中的連接信息放入resolvedDataSources管理
                super.afterPropertiesSet();
                return Boolean.TRUE;
            }
        }catch (ClassNotFoundException | SQLException e) {
            log.error("---程序報錯---:{}", e.getMessage());
        }
        return Boolean.FALSE;
    }
    /**
     * 校驗數據源是否存在
     * @param key 數據源保存的key
     * @return 返回結果,true:存在,false:不存在
     */
    public boolean existsDataSource(String key){
        return Objects.nonNull(this.targetDataSourceMap.get(key));
    }
}

在改造后的DynamicDataSource中,我們添加可以一個 private final Map<Object,Object> targetDataSourceMap,這個map會在添加數據源的配置文件時將創(chuàng)建的Map數據源信息通過DynamicDataSource構造方法進行初始賦值,即:DateSourceConfig類中的createDynamicDataSource()方法中。

同時我們在該類中添加了一個createDataSource方法,進行數據源的創(chuàng)建,并添加到map中,再通過 super.setTargetDataSources(this.targetDataSourceMap) ;進行目標數據源的重新賦值。

2.5.2.3 動態(tài)添加數據源

上述代碼已經實現了添加數據源的方法,那么我們來模擬通過從數據庫表中添加數據源,然后我們通過調用加載數據源的方法將數據源添加進數據源Map中。

在主數據庫中定義一個數據庫表,用于保存數據庫信息。

create table test_db_info(
    id int auto_increment primary key not null comment '主鍵Id',
    url varchar(255) not null comment '數據庫URL',
    username varchar(255) not null comment '用戶名',
    password varchar(255) not null comment '密碼',
    driver_class_name varchar(255) not null comment '數據庫驅動'
    name varchar(255) not null comment '數據庫名稱'
)

為了方便,我們將之前的從庫錄入到數據庫中,修改數據庫名稱。

insert into test_db_info(url, username, password,driver_class_name, name)
value ('jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false',
       'root','123456','com.mysql.cj.jdbc.Driver','add_slave')

數據庫表對應的實體、mapper,小伙伴們自行添加。

啟動SpringBoot時添加數據源:

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 16:56
 **/
@Component
public class LoadDataSourceRunner implements CommandLineRunner {
    @Resource
    private DynamicDataSource dynamicDataSource;
    @Resource
    private TestDbInfoMapper testDbInfoMapper;
    @Override
    public void run(String... args) throws Exception {
        List<TestDbInfo> testDbInfos = testDbInfoMapper.selectList(null);
        if (CollectionUtils.isNotEmpty(testDbInfos)) {
            List<DataSourceEntity> ds = new ArrayList<>();
            for (TestDbInfo testDbInfo : testDbInfos) {
                DataSourceEntity sourceEntity = new DataSourceEntity();
                BeanUtils.copyProperties(testDbInfo,sourceEntity);
                sourceEntity.setKey(testDbInfo.getName());
                ds.add(sourceEntity);
            }
            dynamicDataSource.createDataSource(ds);
        }
    }
}

經過上述SpringBoot啟動后,已經將數據庫表中的數據添加到動態(tài)數據源中,我們調用之前的測試方法,將數據源名稱作為參數傳入看看執(zhí)行結果。

2.5.2.4 測試

通過測試我們發(fā)現數據庫表中的數據庫被動態(tài)加入了數據源中,小伙伴可以愉快地隨意添加數據源了。

好了,今天就跟大家嘮叨到這,希望我的叨叨讓大家對于動態(tài)切換數據源的方式能夠有更深地理解。

以上就是SpringBoot+ThreadLocal+AbstractRoutingDataSource實現動態(tài)切換數據源的詳細內容,更多關于SpringBoot動態(tài)切換數據源的資料請關注腳本之家其它相關文章!

相關文章

  • 自定義注解基本概念和使用方式

    自定義注解基本概念和使用方式

    這篇文章主要介紹了自定義注解基本概念和使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Java實現歸并排序的方法詳解(包含遞歸+非遞歸)

    Java實現歸并排序的方法詳解(包含遞歸+非遞歸)

    歸并歸分n時間復雜度O(nlogn)的歸并排序,遞歸與迭代實現,穩(wěn)定排序適用于大數據處理,本文詳細解析了遞歸與非遞歸版本的實現邏輯與應用場景,需要的朋友可以參考下
    2026-05-05
  • springboot后臺session的存儲與取出方式

    springboot后臺session的存儲與取出方式

    這篇文章主要介紹了springboot后臺session的存儲與取出方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • Java 圖片與byte數組互相轉換實例

    Java 圖片與byte數組互相轉換實例

    下面小編就為大家?guī)硪黄狫ava 圖片與byte數組互相轉換實例。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • Eclipse maven項目lombok安裝配置圖解

    Eclipse maven項目lombok安裝配置圖解

    這篇文章主要介紹了Eclipse maven項目lombok安裝配置圖解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-05-05
  • ElasticSearch合理分配索引分片原理

    ElasticSearch合理分配索引分片原理

    這篇文章主要介紹了ElasticSearch合理分配索引分片原理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • Java靜態(tài)代理與動態(tài)代理案例詳解

    Java靜態(tài)代理與動態(tài)代理案例詳解

    這篇文章主要介紹了Java靜態(tài)代理與動態(tài)代理案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-07-07
  • SpringBoot中配置Redis連接池的完整指南

    SpringBoot中配置Redis連接池的完整指南

    這篇文章主要為大家詳細介紹了SpringBoot中配置Redis連接池的完整指南,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學習一下
    2025-04-04
  • JAVA JNI函數的注冊過程詳細介紹

    JAVA JNI函數的注冊過程詳細介紹

    這篇文章主要介紹了JAVA JNI函數的注冊過程詳細介紹的相關資料,需要的朋友可以參考下
    2016-11-11
  • 分析java并發(fā)中的wait notify notifyAll

    分析java并發(fā)中的wait notify notifyAll

    一個線程修改一個對象的值,而另一個線程則感知到了變化,然后進行相應的操作,這就是wait()、notify()和notifyAll()方法的本質。本文將詳細來介紹它們概念實現以及區(qū)別
    2021-06-06

最新評論

浮梁县| 镇远县| 无极县| 巨鹿县| 峨眉山市| 雅江县| 定安县| 建平县| 涟源市| 全椒县| 南昌市| 吉木乃县| 林西县| 调兵山市| 界首市| 闵行区| 长沙市| 甘孜县| 东山县| 扶沟县| 崇义县| 武汉市| 岳普湖县| 淮阳县| 林西县| 阜平县| 满洲里市| 武定县| 丰台区| 五常市| 靖州| 双柏县| 双城市| 棋牌| 安化县| 桓仁| 遵义县| 宝应县| 探索| 筠连县| 丰宁|