1. 程式人生 > >搞定SpringBoot多資料來源(3):引數化變更源

搞定SpringBoot多資料來源(3):引數化變更源

目錄

  • 1. 引言
  • 2. 引數化變更源說明
    • 2.1 解決思路
    • 2.2 流程說明
  • 3. 實現引數化變更源
    • 3.1 改造動態資料來源
      • 3.1.1 動態資料來源新增功能
      • 3.1.2 動態資料來源配置
    • 3.2 新增資料來源工具類
      • 3.2.1 Spring 上下文工具類
      • 3.2.2 資料來源操作工具
    • 3.3 使用引數變更資料來源
      • 3.3.1 新增查詢資料庫表資訊的 Mapper
      • 3.3.2 定義資料庫連線資訊物件
      • 3.3.3 引數化變更源並查詢表資訊
  • 4. 動態代理消除模板程式碼
    • 4.1 新增 JDK 動態代理
    • 4.2 使用代理實現功能
  • 5. 總結
  • 參考資料
  • 往期文章

春節將至,今天放假了,在此祝小夥伴們新春大吉,身體健康,思路清晰,永遠無BUG!


一句話概括:引數化變更源意思是根據引數動態新增資料來源以及切換資料來源,解決不確定資料來源的問題。

1. 引言

經過前面兩篇文章對於 Spring Boot 處理多個數據庫的策略講解,相信大家已經對多資料來源和動態資料來源有了比較好的瞭解。如需回顧,請見:

  • 《搞定SpringBoot多資料來源(1):多套源策略》
  • 《搞定SpringBoot多資料來源(2):動態資料來源》

在前面文章中,留了一個思考題,無論是多套源還是動態資料來源,相對來說還是固定的資料來源(如一主一從,一主多從等),即在編碼時已經確定的資料庫數量,只是在具體使用哪一個時進行動態處理。如果資料來源本身並不確定,或者說需要根據使用者輸入來連線資料庫,這時,如何處理呢?可以想象現在我們有一個需求,需要對資料庫進行連線管理,使用者可以輸入對應的資料庫連線資訊,然後可以檢視資料庫有哪些表。這就跟平時使用的資料庫管理軟體有點類似了,如 MySQL Workbench、Navicat、SQLyog,下圖是SQLyog截圖:

本文基於前面的示例,新增一個功能,根據使用者輸入的資料庫連線資訊,連線資料庫,並返回資料庫的表資訊。內容包括動態新增資料來源、動態代理簡化資料來源操作等。

本文所涉及到的示例程式碼:https://github.com/mianshenglee/my-example/tree/master/multi-datasource,讀者可結合一起看。

2. 引數化變更源說明

2.1 解決思路

Spring Boot 的動態資料來源,本質上是把多個數據源儲存在一個 Map 中,當需要使用某個資料來源時,從 Map 中獲取此資料來源進行處理。在動態資料來源處理時,通過繼承抽象類 AbstractRoutingDataSource 可實現此功能。既然是 Map ,如果有新的資料來源,把新的資料來源新增到此 Map 中就可以了。這就是整個解決思路。

但是,檢視 AbstractRoutingDataSource 原始碼,可以發現,存放資料來源的 Map targetDataSources 是 private 的,而且並沒有提供對此 Map 本身的操作,它提供的是兩個關鍵操作:setTargetDataSourcesafterPropertiesSet 。其中 setTargetDataSources 設定整個 Map 目標資料來源,afterPropertiesSet 則是對 Map 目標資料來源進行解析,形成最終使用的 resolvedDataSources,可見以下原始碼:

this.resolvedDataSources = new HashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
    Object lookupKey = this.resolveSpecifiedLookupKey(key);
    DataSource dataSource = this.resolveSpecifiedDataSource(value);
    this.resolvedDataSources.put(lookupKey, dataSource);
});

因此,為實現動態新增資料來源到 Map 的功能,我們可以根據這兩個關鍵操作進行處理。

2.2 流程說明

  1. 使用者輸入資料庫連線引數(包括IP、埠、驅動名、資料庫名、使用者名稱、密碼)
  2. 根據資料庫連線引數建立資料來源
  3. 新增資料來源到動態資料來源中
  4. 切換資料來源
  5. 操作資料庫

3. 實現引數化變更源

說明,下面的操作基於之前文章的示例,基本的工程搭建及配置不再重複說明,有需要可參考文章。

3.1 改造動態資料來源

3.1.1 動態資料來源新增功能

為了可以動態新增資料來源到 Map ,我們需要對動態資料來源進行改造。如下:

public class DynamicDataSource extends AbstractRoutingDataSource {
    private Map<Object, Object> backupTargetDataSources;

    /**
     * 自定義建構函式
     */
    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSource){
        backupTargetDataSources = targetDataSource;
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(backupTargetDataSources);
        super.afterPropertiesSet();
    }

    /**
     * 新增新資料來源
     */
    public void addDataSource(String key, DataSource dataSource){
        this.backupTargetDataSources.put(key,dataSource);
        super.setTargetDataSources(this.backupTargetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getContextKey();
    }
}
  • 添加了自定義的 backupTargetDataSources 作為原 targetDataSources 的拷貝
  • 自定義建構函式,把需要儲存的目標資料來源拷貝到自定義的 Map 中
  • 新增新資料來源時,依然使用 setTargetDataSourcesafterPropertiesSet 完成新資料來源新增。
  • 注意:afterPropertiesSet 的作用很重要,它負責解析成可用的目標資料來源。

3.1.2 動態資料來源配置

原來在建立動態資料來源時,使用的是無引數建構函式,經過前面改造後,使用有參建構函式,如下:

@Bean
@Primary
public DataSource dynamicDataSource() {
    Map<Object, Object> dataSourceMap = new HashMap<>(2);
    dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
    dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
    //有參建構函式
    return new DynamicDataSource(masterDataSource(), dataSourceMap);
}

3.2 新增資料來源工具類

3.2.1 Spring 上下文工具類

在Spring Boot 使用過程中,經常會用到 Spring 的上下文,常見的就是從 Spring 的 IOC 中獲取 bean 來進行操作。由於 Spring 使用的 IOC 基本上把 bean 都注入到容器中,因此需要 Spring 上下文來獲取。我們在 context 包下新增 SpringContextHolder ,如下:

@Component
public class SpringContextHolder implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
    }
    /**
     * 返回上下文
     */
    public static ApplicationContext getContext(){
        return SpringContextHolder.applicationContext;
    }
}

通過 getContext 就可以獲取上下文,進而操作。

3.2.2 資料來源操作工具

通過引數新增資料來源,需要根據引數構造資料來源,然後新增到前面說的 Map 中。如下:

public class DataSourceUtil {
    /**
     * 建立新的資料來源,注意:此處只針對 MySQL 資料庫
     */
    public static DataSource makeNewDataSource(DbInfo dbInfo){
        String url = "jdbc:mysql://"+dbInfo.getIp() + ":"+dbInfo.getPort()+"/"+dbInfo.getDbName()
                +"?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8";
        String driveClassName = StringUtils.isEmpty(dbInfo.getDriveClassName())? "com.mysql.cj.jdbc.Driver":dbInfo.getDriveClassName();
        return DataSourceBuilder.create().url(url)
                .driverClassName(driveClassName)
                .username(dbInfo.getUsername())
                .password(dbInfo.getPassword())
                .build();
    }

    /**
     * 新增資料來源到動態源中
     */
    public static void addDataSourceToDynamic(String key, DataSource dataSource){
        DynamicDataSource dynamicDataSource = SpringContextHolder.getContext().getBean(DynamicDataSource.class);
        dynamicDataSource.addDataSource(key,dataSource);
    }

    /**
     * 根據資料庫連線資訊新增資料來源到動態源中
     * @param key
     * @param dbInfo
     */
    public static void addDataSourceToDynamic(String key, DbInfo dbInfo){
        DataSource dataSource = makeNewDataSource(dbInfo);
        addDataSourceToDynamic(key,dataSource);
    }
}
  • 通過 DataSourceBuilder 及相應的引數來構造資料來源,注意此處只針對 MySQL 作處理,其它資料庫的話,對應的 url 及 DriveClassName 需作相應的變更。
  • 新增資料來源時,通過 Spring 上下文獲取動態資料來源的 bean,然後新增。

3.3 使用引數變更資料來源

前面兩步已實現新增資料來源,下面我們根據需求(根據使用者輸入的資料庫連線資訊,連線資料庫,並返回資料庫的表資訊),看看如何使用它。

3.3.1 新增查詢資料庫表資訊的 Mapper

通過 MySQL 的 information_schema 可以獲取表資訊。

@Repository
public interface TableMapper extends BaseMapper<TestUser> {
    /**
     * 查詢表資訊
     */
    @Select("select table_name, table_comment, create_time, update_time " +
            " from information_schema.tables " +
            " where table_schema = (select database())")
    List<Map<String,Object>> selectTableList();
}

3.3.2 定義資料庫連線資訊物件

把資料庫連線資訊通過一個類進行封裝。

@Data
public class DbInfo {
    private String ip;
    private String port;
    private String dbName;
    private String driveClassName;
    private String username;
    private String password;
}

3.3.3 引數化變更源並查詢表資訊

在 controller 層,我們定義一個查詢表資訊的介面,根據傳入的引數,連線資料來源,返回表資訊:

/**
 * 根據資料庫連線資訊獲取表資訊
 */
@GetMapping("table")
public Object findWithDbInfo(DbInfo dbInfo) throws Exception {
    //資料來源key
    String newDsKey = System.currentTimeMillis()+"";
    //新增資料來源
    DataSourceUtil.addDataSourceToDynamic(newDsKey,dbInfo);
    DynamicDataSourceContextHolder.setContextKey(newDsKey);
    //查詢表資訊
    List<Map<String, Object>> tables = tableMapper.selectTableList();
    DynamicDataSourceContextHolder.removeContextKey();
    return ResponseResult.success(tables);
}
  • 訪問地址 http://localhost:8080/dd/table?ip=localhost&port=3310&dbName=mytest&username=root&password=111111 ,對應資料庫連線引數。
  • 此處資料來源的 key 是無意義的,建議根據實際場景設定有意義的值

4. 動態代理消除模板程式碼

前面已經完成了引數化切換資料來源功能,但還有一點就是有模板程式碼,如新增資料來源、切換資料來源、對此資料來源進行CURD操作、釋放資料來源,如果每個地方都這樣做,就很繁瑣,這個時候,就需要用到動態代理了,可引數我之前的文章:java開發必學知識:動態代理。此處,使用 JDK 自帶的動態代理,實現引數化變更資料來源的功能,消除模板程式碼。

4.1 新增 JDK 動態代理

新增 proxy 包,新增 JdkParamDsMethodProxy 類,實現 InvocationHandler 介面,在 invoke 中編寫引數化切換資料來源的邏輯即可。如下:

public class JdkParamDsMethodProxy implements InvocationHandler {
    // 代理物件及相應引數
    private String dataSourceKey;
    private DbInfo dbInfo;
    private Object targetObject;
    public JdkParamDsMethodProxy(Object targetObject, String dataSourceKey, DbInfo dbInfo) {
        this.targetObject = targetObject;
        this.dataSourceKey = dataSourceKey;
        this.dbInfo = dbInfo;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //切換資料來源
        DataSourceUtil.addDataSourceToDynamic(dataSourceKey, dbInfo);
        DynamicDataSourceContextHolder.setContextKey(dataSourceKey);
        //呼叫方法
        Object result = method.invoke(targetObject, args);
        DynamicDataSourceContextHolder.removeContextKey();
        return result;
    }

    /**
     * 建立代理
     */
    public static Object createProxyInstance(Object targetObject, String dataSourceKey, DbInfo dbInfo) throws Exception {
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader()
                , targetObject.getClass().getInterfaces(), new JdkParamDsMethodProxy(targetObject, dataSourceKey, dbInfo));
    }
}
  • 程式碼中,需要使用的引數通過建構函式傳入
  • 通過 Proxy.newProxyInstance 建立代理,在方法執行時( invoke ) 進行資料來源新增、切換、資料庫操作、清除等

4.2 使用代理實現功能

有了代理,在新增和切換資料來源時就可以擦除模板程式碼,前面的業務程式碼就變成:

@GetMapping("table")
    public Object findWithDbInfo(DbInfo dbInfo) throws Exception {
        //資料來源key
        String newDsKey = System.currentTimeMillis()+"";
        //使用代理切換資料來源
        TableMapper tableMapperProxy = (TableMapper)JdkParamDsMethodProxy.createProxyInstance(tableMapper, newDsKey, dbInfo);
        List<Map<String, Object>> tables = tableMapperProxy.selectTableList();
        return ResponseResult.success(tables);
    }

通過代理,程式碼就簡潔多了。

5. 總結

本文基於動態資料來源,對引數化變更資料來源及應用場景進行了說明,提出連線資料庫,查詢表資訊的功能需求作為示例,實現根據引數構建資料來源,動態新增資料來源功能,對引數化變更資料來源的使用進行講解,最後使用動態代理簡化操作。本篇文章偏重程式碼實現,小夥伴們可以新手實踐來加深認知。

本文配套的示例,示例程式碼,有興趣的可以執行示例來感受一下。

參考資料

  • Spring主從資料庫的配置和動態資料來源切換原理: https://www.liaoxuefeng.com/article/1182502273240832
  • 談談Spring Boot 資料來源載入及其多資料來源簡單實現: https://juejin.im/post/5cb0023d5188250df17d4ffc

往期文章

  • 搞定SpringBoot多資料來源(2):動態資料來源
  • 搞定SpringBoot多資料來源(1):多套源策略
  • java開發必學知識:動態代理
  • 2019 讀過的好書推薦
  • springboot+apache前後端分離部署https
  • springboot+logback 日誌輸出企業實踐(下)
  • springboot+logback 日誌輸出企業實踐(上)

我的公眾號(搜尋Mason技術記錄),獲取更多技術記錄:

相關推薦

SpringBoot資料來源(3)引數變更

目錄 1. 引言 2. 引數化變更源說明 2.1 解決思路 2.2 流程說明 3. 實現引數化變更源 3.1 改造動態資料來源 3.1.1 動

SpringBoot資料來源(1)策略

目錄 1. 引言 2. 執行環境 3. 多套資料來源 3.1 搭建 Spring Boot 工程 3.1.1 初始化 Spring Boot 工程 3.1.2 新增 MyBat

SpringBoot學習筆記(三)SpringBoot整合Mybatis、SpringBoot事務管理、SpringBoot資料來源

SpringBoot整合Mybatis 第一步我們需要在pom.xml裡面引入mybatis相關的jar包 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artif

Tensorflow實現Mask R-CNN實例分割通用框架,檢測,分割和特征點定位一次圖)

優點 設計 orf 時間 rcnn 超越 rain 沒有 add Mask R-CNN實例分割通用框架,檢測,分割和特征點定位一次搞定(多圖) 導語:Mask R-CNN是Faster R-CNN的擴展形式,能夠有效地檢測圖像中的目標,同時還能為每個實例生成一個

python線程和進程

觸發 不一定 並不會 守護線程 執行 comm mes 權限 pipe 1.1 線程 1.1.1 什麽是線程 線程是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以並發多個線程,每條線程

分散式鎖, 註解形式, SpringBoot定時任務@Scheduled 在叢集下的優化

SpringBoot提供了 Schedule模組完美支援定時任務的執行 在實際開發中由於專案部署在分散式或叢集伺服器上 會導致定時任務多次觸發 因此,使用redis分佈鎖機制可以有效避免多次執行定時任務   核心方法是org.springframework.data.redis

mybatis資料來源錯誤org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

使用Spring Boot2.0整合mybatis多資料來源遇到如下錯誤 錯誤資訊: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.boot.mapper.one.Sc

SpringBoot資料來源從庫第一次執行SQL很慢的原因

今天開發時偶爾發現從庫的第一次SQL執行的速度很慢,所以就進行了一系列排查,後來發現是因為多資料來源的情況下,從庫都是採用懶載入的形式進行載入的,所以就會出現第一次執行SQL很慢的現象,可以從以下兩點進行證明: 證明一 這是專案剛啟動的時候,主庫進行了初始化: 而當訪問

SpringBoot資料來源連線池超時配置(MySQL+SQLServer)

單資料來源超時配置 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/dcmserver?characterEncoding=utf-8 spring.datasource.username=root spring.data

python執行緒和程序

參考 https://www.cnblogs.com/whatisfantasy/p/6440585.html 1 概念梳理: 1.1 執行緒 1.1.1 什麼是執行緒 執行緒是作業系統能夠進行運算排程的最小單位。它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個

SpringBoot資料來源以及資料來源事物

請耐心看完 注意:本示例用的是springboot2.0 你們在專案中有使用到多資料來源嗎? 原理使用根據包名,載入不同的資料來源,也可以根據註解載入不同的資料來源 直接奔向主題,請繫好安全帶!!! 配置兩個不同的資料來源 #datasource1 sprin

[Java] springboot資料來源以及讀寫分離

springboot 資料來源路由器    --------------------  分割線  ------------------------- 上面是資料來源繫結,繫結之後接下來就是讀寫分離 在讀寫分離中,很明顯就是將事物型sql轉到主庫,將查詢sql轉入從庫 在進行讀

Java後端愛上SpringBoot 第四節:SpringBoot資料來源

Java後端愛上SpringBoot 第四節:SpringBoot多資料來源 配置一個Oracle資料庫 配置一個SQLServer資料庫 配置一個MySql資料庫 PS:配置多資料來源這個事情是專案中用到的,之前也做過dome,但是做的時候又

MySQL3表查詢操作

轉載:https://blog.csdn.net/Anarkh_Lee/article/details/79856935 1.交叉連線查詢(基本不會使用——得到的是兩個表的乘積) 語法:select * from A,B; 2.內連線查詢

SpringBoot資料來源解決方案

SpringBoot多資料來源解決方案 Java大哥大 2018-12-28 16:14:51 近在做一個數據統計的專案,這裡涉及到多個數據庫的資料統計,所以找到了一個不錯的解決方案。 寫一篇部落格作為筆記。 例子介紹 類庫:例子中使用的是MyBatis Plus和Dy

我的畢設終於了!題為利用Python開發一款遊戲!

    現在來看一下實現的過程。 外形 俄羅斯方塊整個介面分為兩部分,一部分是左邊的遊戲區域,另一部分是右邊的顯示區域,顯示得分、速度、下一個方塊樣式等。這裡就不放截圖了,看上圖就可以。 遊戲區域跟貪吃蛇一樣,是由一個個小方格組成的,為了看得直觀,

10分鐘輕鬆SpringBoot整合RabbitMQ教程

第一步:在專案pom.xml檔案中,新增pring-boot-starter-amqp 依賴<dependency> <groupId>org.springframework.boot</groupId> <artifac

歷史上最詳細的SpringBoot資料來源配置及使用流程

關於標題—>只是想看看標題黨的效果如何 一、場景(簡化) 要實現一個使用者資料中心的系統架構,其中要用到兩個資料庫,一個database名為sso,主要存放使用者的資訊。另一個database名為configuration,存放一些配置資訊。當

SpringBoot 資料來源配置

SpringBoot 多資料來源配置,查詢了好多方法,只有這個最好用. SpringBoot 多資料來源配置 檔案結構: DateSourceMultiple DataSourceType.java 註解動態切換資料庫 DruidConfig.java 載入資料來源

十分鐘SpringBoot 和Redis 實戰整合

這裡,對於springboot和redis 不做介紹,大家可以自己去查詢資料~~~~~~~~~~~ windows下本地安裝 redis 安裝 話不多說,來就是開搞! 專案框架 具體實現程式碼 pom檔案 <?xml version="1.0" en