SpringBoot配置動態(tài)數(shù)據(jù)源(可在運(yùn)行時添加)

一、前言

最近參與了一個大公司的wms項(xiàng)目,采用 SpringCloud 技術(shù)棧開發(fā)育灸,公司國內(nèi)國外有非常多的工廠,針對每個國家單獨(dú)部署一套wms系統(tǒng)昵宇,大體的邏輯相通描扯,只有個別調(diào)整。
項(xiàng)目的數(shù)據(jù)庫是這樣設(shè)計(jì)的趟薄,每個國家的所有人員信息存到一個庫中绽诚,稱為基礎(chǔ)資料(base_info)庫,每個國家的每個工廠單獨(dú)啟用一個數(shù)據(jù)庫。
所以中國區(qū)的項(xiàng)目數(shù)據(jù)庫結(jié)構(gòu)類似這樣:

base_info
db_0
db_1
db_2
db_sc
db_sd
...

但是項(xiàng)目中是這樣做多數(shù)據(jù)源配置的:

  • 首先在配置文件中配置國內(nèi)所有的數(shù)據(jù)庫信息;
  • 為每個數(shù)據(jù)庫配置創(chuàng)建一個數(shù)據(jù)源Bean恩够;
  • 創(chuàng)建一個動態(tài)數(shù)據(jù)源 Bean 卒落,將上步創(chuàng)建的所有數(shù)據(jù)源注入到動態(tài)數(shù)據(jù)源中;
  • 創(chuàng)建會話工廠 Bean蜂桶、事務(wù)管理器 Bean儡毕;

項(xiàng)目在人員登錄的時候做了配置,可以自動將數(shù)據(jù)源切換為人員對應(yīng)的數(shù)據(jù)庫上扑媚。

這里就產(chǎn)生了問題腰湾,國內(nèi)那么多的工廠,需要配置那么多的數(shù)據(jù)庫疆股,并且手動配置那么多的數(shù)據(jù)源费坊,雖然可以實(shí)現(xiàn)數(shù)據(jù)源切換,但是繁瑣的配置比較費(fèi)勁旬痹,所以我稱之為此種配置方式為 “靜態(tài)多數(shù)據(jù)源”附井。

二、動態(tài)多數(shù)據(jù)源

2.1 什么是動態(tài)多數(shù)據(jù)源

簡單來說两残,可以在應(yīng)用運(yùn)行中永毅,將數(shù)據(jù)源動態(tài)生成并可以切換使用。

2.2 動態(tài)多數(shù)據(jù)源好處

  • 省去了創(chuàng)建大量的 Bean 的操作人弓;
  • 可以在運(yùn)行時添加數(shù)據(jù)源沼死;

三、動態(tài)多數(shù)據(jù)源的配置

廢話不多說崔赌,直接上干貨意蛀。

3.1 創(chuàng)建動態(tài)數(shù)據(jù)源

通過實(shí)現(xiàn)Spring提供的AbstractRoutingDataSource類,我們可以實(shí)現(xiàn)自己的數(shù)據(jù)源選擇邏輯峰鄙,從而可以實(shí)現(xiàn)數(shù)據(jù)源的動態(tài)切換。

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Value("${spring.datasource.default-db-key}")
    private String defaultDbKey;·

    @Override
    protected Object determineCurrentLookupKey() {
        String currentDb = DynamicDataSourceService.currentDb();
        if (currentDb == null) {
            return defaultDbKey;
        }
        return currentDb;
    }
}

3.2 創(chuàng)建動態(tài)數(shù)據(jù)源配置類

跟配置靜態(tài)多數(shù)據(jù)源一樣太雨,需要手動配置下面的三個 Bean吟榴,只不過DynamicDataSource類的targetDataSources是空的。

@Configuration
public class DynamicDataSourceConfig {
    /**
     * 動態(tài)數(shù)據(jù)源
     */
    @Bean
    public DynamicDataSource dynamicDataSource() {
        DynamicDataSource dataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        dataSource.setTargetDataSources(targetDataSources);
        return dataSource;
    }

    /**
     * 會話工廠
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        sqlSessionFactoryBean.setConfiguration(configuration);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/repository/*.xml"));
        return sqlSessionFactoryBean;
    }

    /**
     * 事務(wù)管理器
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

3.3 創(chuàng)建動態(tài)數(shù)據(jù)源服務(wù)類

這是一個比較核心的工具類,提供了一些靜態(tài)方法從而可以實(shí)現(xiàn)一些功能囊扳,包括:
動態(tài)添加數(shù)據(jù)源吩翻、切換數(shù)據(jù)源、重置數(shù)據(jù)源锥咸、獲取數(shù)據(jù)源狭瞎。
在我們 3.1 中創(chuàng)建的 DynamicDataSource 類中,我們就是調(diào)用了 DynamicDataSourceService 類的 switchDb
方法實(shí)現(xiàn)的數(shù)據(jù)源選擇搏予。
通過查看下面的代碼就能看出來使用線程本地的技術(shù)實(shí)現(xiàn)的多個請求數(shù)據(jù)源互不相干熊锭。

public class DynamicDataSourceService {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceService.class);

    private static final Map<Object, Object> dataSources = new HashMap<>();
    private static final ThreadLocal<String> dbKeys = ThreadLocal.withInitial(() -> null);

    /**
     * 動態(tài)添加一個數(shù)據(jù)源
     *
     * @param name       數(shù)據(jù)源的key
     * @param dataSource 數(shù)據(jù)源對象
     */
    public static void addDataSource(String name, DataSource dataSource) {
        DynamicDataSource dynamicDataSource = App.context.getBean(DynamicDataSource.class);
        dataSources.put(name, dataSource);
        dynamicDataSource.setTargetDataSources(dataSources);
        dynamicDataSource.afterPropertiesSet();

        log.info("添加了數(shù)據(jù)源:{}",name);
    }

    /**
     * 切換數(shù)據(jù)源
     */
    public static void switchDb(String dbKey) {
        dbKeys.set(dbKey);
    }

    /**
     * 重置數(shù)據(jù)源
     */
    public static void resetDb() {
        dbKeys.remove();
    }

    /**
     * 獲取當(dāng)前數(shù)據(jù)源
     */
    public static String currentDb() {
        return dbKeys.get();
    }
}

至此,動態(tài)多數(shù)據(jù)源的配置就完成了,我們只需要編寫數(shù)據(jù)源生成的邏輯碗殷,在程序運(yùn)行時調(diào)用 addDataSource 方法即可將數(shù)據(jù)源動態(tài)添加到上下文中精绎,并支持動態(tài)切換。

下面簡單介紹一下基于配置文件的數(shù)據(jù)源生成锌妻。

四代乃、數(shù)據(jù)源生成器

我自定義了一個數(shù)據(jù)源生成器接口用于定義動態(tài)生成數(shù)據(jù)源的要求。

public interface DataSourceProvider {
    List<DataSource> provide();
}

然后編寫了一個根據(jù)配置文件提供數(shù)據(jù)源并配置的實(shí)現(xiàn)類仿粹。

@Component
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public class YmlDataSourceProvider implements DataSourceProvider {

    private List<Map<String, DataSourceProperties>> dataSources;

    private DataSource buildDataSource(DataSourceProperties prop) {
        DataSourceBuilder<?> builder = DataSourceBuilder.create();
        builder.driverClassName(prop.getDriverClassName());
        builder.username(prop.getUsername());
        builder.password(prop.getPassword());
        builder.url(prop.getJdbcUrl());
        return builder.build();
    }

    @Override
    public List<DataSource> provide() {
        List<DataSource> res = new ArrayList<>();
        dataSources.forEach(map -> {
            Set<String> keys = map.keySet();
            keys.forEach(key -> {
                DataSourceProperties properties = map.get(key);
                DataSource dataSource = buildDataSource(properties);
                DynamicDataSourceService.addDataSource(key, dataSource);
            });
        });
        return res;
    }

    @PostConstruct
    public void init() {
        provide();
    }

    public List<Map<String, DataSourceProperties>> getDataSources() {
        return dataSources;
    }

    public void setDataSources(List<Map<String, DataSourceProperties>> dataSources) {
        this.dataSources = dataSources;
    }
}

看一下對應(yīng)的配置文件內(nèi)容:

spring:
  datasource:
    default-db-key: db0
    hikari:
      data-sources:
        - db0:
            jdbc-url: jdbc:mysql://47.96.172.163:3306/test
            username: admin
            password: 199711
            driver-class-name: com.mysql.cj.jdbc.Driver
        - db1:
            jdbc-url: jdbc:mysql://47.96.172.163:3306/test2
            username: admin
            password: 199711
            driver-class-name: com.mysql.cj.jdbc.Driver

這樣就實(shí)現(xiàn)了應(yīng)用啟動時自動將配置文件中的數(shù)據(jù)源配置讀取并生成數(shù)據(jù)源注冊到上下文中搁吓。
當(dāng)然也可以有其他的實(shí)現(xiàn),比如從數(shù)據(jù)庫讀取并配置吭历,或者通過接口請求的方式生成都可以堕仔,只要實(shí)現(xiàn)自己的 DataSourceProvider 就可以了。

五毒涧、后記

動態(tài)配置多數(shù)據(jù)源到這里就結(jié)束了贮预,一些需要優(yōu)化的地方可以自己修改,比如使用切面的形式將選擇數(shù)據(jù)源的邏輯抽離等等契讲。
借助 Spring 的生態(tài)仿吞,許多的工作都變得異常簡單。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捡偏,一起剝皮案震驚了整個濱河市唤冈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌银伟,老刑警劉巖你虹,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異彤避,居然都是意外死亡傅物,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門琉预,熙熙樓的掌柜王于貴愁眉苦臉地迎上來董饰,“玉大人,你說我怎么就攤上這事圆米∽湓荩” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵娄帖,是天一觀的道長也祠。 經(jīng)常有香客問我,道長近速,這世上最難降的妖魔是什么诈嘿? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任堪旧,我火速辦了婚禮,結(jié)果婚禮上永淌,老公的妹妹穿的比我還像新娘崎场。我一直安慰自己,他們只是感情好遂蛀,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布谭跨。 她就那樣靜靜地躺著,像睡著了一般李滴。 火紅的嫁衣襯著肌膚如雪螃宙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天所坯,我揣著相機(jī)與錄音谆扎,去河邊找鬼。 笑死芹助,一個胖子當(dāng)著我的面吹牛堂湖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播状土,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼无蜂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蒙谓?” 一聲冷哼從身側(cè)響起斥季,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎累驮,沒想到半個月后酣倾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谤专,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年躁锡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片置侍。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡映之,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墅垮,到底是詐尸還是另有隱情惕医,我是刑警寧澤耕漱,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布算色,位于F島的核電站,受9級特大地震影響螟够,放射性物質(zhì)發(fā)生泄漏灾梦。R本人自食惡果不足惜峡钓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望若河。 院中可真熱鬧能岩,春花似錦、人聲如沸萧福。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲫忍。三九已至膏燕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悟民,已是汗流浹背坝辫。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留射亏,地道東北人近忙。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像智润,于是被迫代替她去往敵國和親及舍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容