SpringBoot配置多數(shù)據(jù)源

在最近的開發(fā)中需要在業(yè)務(wù)數(shù)據(jù)庫(kù)之外訪問(wèn)大數(shù)據(jù)提供的數(shù)據(jù)绷柒,所以使用到了多數(shù)據(jù)源。下面就講一下在SpringBoot中如何配置多數(shù)據(jù)源躯概。

一、方法介紹

我們使用org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource來(lái)完成數(shù)據(jù)源的切換流纹。在AbstractRoutingDataSource中Spring使用Map來(lái)管理數(shù)據(jù)源,在對(duì)象初始化完成后會(huì)將配置的對(duì)象放入Map<Object, DataSource> resolvedDataSources违诗。

@Override
public void afterPropertiesSet() {
    if (this.targetDataSources == null) {
        throw new IllegalArgumentException("Property 'targetDataSources' is required");
    }
    this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
    for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
        Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
        DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
        this.resolvedDataSources.put(lookupKey, dataSource);
    }
    if (this.defaultTargetDataSource != null) {
        this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
    }
}

既然在存儲(chǔ)時(shí)使用到了map漱凝,那么獲取的時(shí)候獲取的時(shí)候我們也得提供這樣一個(gè)key。

/**
 * Retrieve the current target DataSource. Determines the
 * {@link #determineCurrentLookupKey() current lookup key}, performs
 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
 * falls back to the specified
 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
 * @see #determineCurrentLookupKey()
 */
protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
    }
    if (dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    }
    return dataSource;
}

/**
 * Determine the current lookup key. This will typically be
 * implemented to check a thread-bound transaction context.
 * <p>Allows for arbitrary keys. The returned key needs
 * to match the stored lookup key type, as resolved by the
 * {@link #resolveSpecifiedLookupKey} method.
 */
protected abstract Object determineCurrentLookupKey();

從代碼中我們可以看到较雕,這個(gè)key是由抽象方法determineCurrentLookupKey提供碉哑,所以我們需要重寫這個(gè)方法,提供我們自己生成key的方法亮蒋。

二扣典、實(shí)現(xiàn)多數(shù)據(jù)源選擇器

首先,我們定以一個(gè)枚舉用來(lái)存放數(shù)據(jù)源的key慎玖。

public enum DataSourceType {
    // 大數(shù)據(jù)源
    BigData,
    // 主業(yè)務(wù)源
    Business;
}

這里我設(shè)置了BigDataBusiness兩個(gè)key贮尖,分別用來(lái)標(biāo)識(shí)主業(yè)務(wù)數(shù)據(jù)和大數(shù)據(jù)的數(shù)據(jù)源。現(xiàn)在我們繼承AbstractRoutingDataSource類并重寫determineCurrentLookupKey方法趁怔。

public class DynamicDataSource extends AbstractRoutingDataSource {

    // 數(shù)據(jù)源類型集合
    private static final List<DataSourceType> dataSourceTypes = Lists.newArrayList();

    //線程本地環(huán)境
    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

    //設(shè)置數(shù)據(jù)源
    public static void setDataSourceType(DataSourceType routingType) {
        contextHolder.set(routingType);
    }

    public static void addDataSourceType(DataSourceType dataSourceType) {
        if (dataSourceType != null) {
            dataSourceTypes.add(dataSourceType);
        }
    }

    public static void reset() {
        contextHolder.remove();
    }

    public static boolean containsDataSource(DataSourceType routingType) {
        return dataSourceTypes.contains(routingType);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return contextHolder.get();
    }
}

在上面的動(dòng)態(tài)數(shù)據(jù)源中使用ThreadLocal來(lái)存放當(dāng)前的數(shù)據(jù)源類型湿硝,保證每一個(gè)線程都獲取到自己想要的數(shù)據(jù)源類型。下面我們通過(guò)切面來(lái)指定數(shù)據(jù)源润努。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    DataSourceType type();
}

@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {

    @Around("@annotation(targetDataSource)")
    public Object targetDataSource(ProceedingJoinPoint pjp, TargetDataSource targetDataSource) throws Throwable {
        DataSourceType dataSourceType = targetDataSource.type();

        if (DynamicDataSource.containsDataSource(dataSourceType)) {
            DynamicDataSource.setDataSourceType(targetDataSource.type());
        }
        try {
            return pjp.proceed();
        } finally {
            DynamicDataSource.reset();
        }
    }
}

對(duì)于使用了TargetDataSource注解的方法关斜,我們通過(guò)環(huán)繞通知(Around)在方法調(diào)用前設(shè)置ThreadLocal中的數(shù)據(jù)源類型為注解中指定的類型,因?yàn)樘幵诮y(tǒng)一個(gè)線程中铺浇,之后determineCurrentLookupKey方法就能夠獲取到指定的key痢畜,進(jìn)而得到我們需要的數(shù)據(jù)源。

三鳍侣、配置數(shù)據(jù)源

前面我們已經(jīng)設(shè)置好了多數(shù)據(jù)元切換的選擇器了丁稀,現(xiàn)在我們要提供出多個(gè)數(shù)據(jù)源,并將它們放入到選擇器當(dāng)中倚聚。具體方法如下:

@Configuration
@MapperScan(basePackages = "com.song.study.multidatasource.db.mapper")
@PropertySource(value = "classpath:dataSource.properties")
public class DynamicDataSourceConfig implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    private DataSource businessDS;
    private DataSource bigDateDS;

    @Override
    public void setEnvironment(Environment environment) {
        businessDS = initDataSource(environment, DataSourceType.BigData.name());
        bigDateDS = initDataSource(environment, DataSourceType.Business.name());
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<Object, Object> targetDataSources = new HashMap();
        targetDataSources.put(DataSourceType.Business, businessDS);
        targetDataSources.put(DataSourceType.BigData, bigDateDS);

        DynamicDataSource.addDataSourceType(DataSourceType.BigData);
        DynamicDataSource.addDataSourceType(DataSourceType.Business);

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);
    }

    public DataSource initDataSource(Environment environment, String prefix) {
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, prefix);

        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setPoolName(prefix + "HikariDataSourcePool");

        hikariConfig.setDriverClassName(propertyResolver.getProperty(".database.driverClassName"));
        hikariConfig.setJdbcUrl(propertyResolver.getProperty(".database.url"));
        hikariConfig.setUsername(propertyResolver.getProperty(".database.username"));
        hikariConfig.setPassword(propertyResolver.getProperty(".database.password"));
        hikariConfig.setMaxLifetime(propertyResolver.getProperty(".connection.maxLifeTime", Integer.class, 120000));
        hikariConfig.setConnectionTimeout(propertyResolver.getProperty(".connection.timeout", Integer.class, 2000));
        hikariConfig.setMinimumIdle(propertyResolver.getProperty(".pool.minPoolSize", Integer.class, 20));
        hikariConfig.setMaximumPoolSize(propertyResolver.getProperty(".pool.maxPoolSize", Integer.class, 300));
        hikariConfig.setConnectionInitSql("SELECT 1");

        HikariDataSource dataSource = new HikariDataSource(hikariConfig);
        return dataSource;
    }
}

四线衫、使用數(shù)據(jù)源

接下來(lái)只要在調(diào)用數(shù)據(jù)庫(kù)操作的方法上添加TargetDataSource注解就好了。

@Service
public class SampleServiceImpl implements SampleService {
    @Autowired
    private BusinessRepository businessRepository;

    @Autowired
    private BigDataRepository bigDataRepository;

    @Override
    @TargetDataSource(type = DataSourceType.Business)
    public BusinessPO getBusiness(Long id) {
        return businessRepository.getById(id);
    }

    @Override
    @TargetDataSource(type = DataSourceType.BigData)
    public BigDataPO getBigData(Long id) {
        return bigDataRepository.getById(id);
    }
}

完整example見Github(iceSong/multi-data-source)惑折。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末授账,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子唬复,更是在濱河造成了極大的恐慌矗积,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敞咧,死亡現(xiàn)場(chǎng)離奇詭異棘捣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門乍恐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)评疗,“玉大人,你說(shuō)我怎么就攤上這事茵烈“俅遥” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵呜投,是天一觀的道長(zhǎng)加匈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)仑荐,這世上最難降的妖魔是什么雕拼? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮粘招,結(jié)果婚禮上啥寇,老公的妹妹穿的比我還像新娘。我一直安慰自己洒扎,他們只是感情好辑甜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著袍冷,像睡著了一般磷醋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胡诗,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天子檀,我揣著相機(jī)與錄音,去河邊找鬼乃戈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛亩进,可吹牛的內(nèi)容都是我干的症虑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼归薛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谍憔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起主籍,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤习贫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后千元,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苫昌,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年幸海,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祟身。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奥务。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖袜硫,靈堂內(nèi)的尸體忽然破棺而出氯葬,到底是詐尸還是另有隱情,我是刑警寧澤婉陷,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布帚称,位于F島的核電站,受9級(jí)特大地震影響秽澳,放射性物質(zhì)發(fā)生泄漏闯睹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一肝集、第九天 我趴在偏房一處隱蔽的房頂上張望瞻坝。 院中可真熱鬧,春花似錦杏瞻、人聲如沸所刀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浮创。三九已至,卻和暖如春砌函,著一層夾襖步出監(jiān)牢的瞬間斩披,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工讹俊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留垦沉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓仍劈,卻偏偏與公主長(zhǎng)得像厕倍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贩疙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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