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

淺析SpringBoot多數(shù)據(jù)源實現(xiàn)方案

 更新時間:2024年02月20日 15:51:18   作者:半__夏  
現(xiàn)在很多項目的開發(fā)過程中,可能涉及到多個數(shù)據(jù)源,像讀寫分離的場景,或者因為業(yè)務(wù)復(fù)雜,導(dǎo)致不同的業(yè)務(wù)部署在不同的數(shù)據(jù)庫上,那么這樣的場景,我們應(yīng)該如何在代碼中簡潔方便的切換數(shù)據(jù)源呢,本文介紹SpringBoot多數(shù)據(jù)源實現(xiàn)方案,感興趣的朋友跟隨小編一起看看吧

SpringBoot多數(shù)據(jù)源實現(xiàn)方案

現(xiàn)在很多項目的開發(fā)過程中,可能涉及到多個數(shù)據(jù)源,像讀寫分離的場景,或者因為業(yè)務(wù)復(fù)雜,導(dǎo)致不同的業(yè)務(wù)部署在不同的數(shù)據(jù)庫上,那么這樣的場景,我們應(yīng)該如何在代碼中簡潔方便的切換數(shù)據(jù)源呢?分析這個需求,我們發(fā)現(xiàn)要做的事情無非兩件

  • 構(gòu)建多個數(shù)據(jù)源
  • 封裝一個模塊能實現(xiàn)動態(tài)的切換數(shù)據(jù)源,且數(shù)據(jù)源的切換代碼應(yīng)該盡量和業(yè)務(wù)進行解耦

構(gòu)建多個數(shù)據(jù)源

構(gòu)建多個數(shù)據(jù)源其實比較簡單,和構(gòu)建一個數(shù)據(jù)源是類似的。在SpringBoot中,只需要做三件事

  • 將數(shù)據(jù)庫的配置注冊到配置文件中
  • 選擇一個數(shù)據(jù)庫連接池來構(gòu)建數(shù)據(jù)源,我們這里用阿里出品的Druid
  • 選擇一個orm框架來實現(xiàn)基本的sql,我們這里選用Mybatis

springboot配置文件

spring:
  datasource:
    master:
      url: jdbc:mysql://localhost:3306/db_master
      username: root
      password: ******
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
    slave:
      url: jdbc:mysql://localhost:3306/db_slave
      username: root
      password: Hxy@950504
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
mybatis:
  mapper-locations: classpath:mapper/**/*.xml

注冊多個數(shù)據(jù)源

@Configuration
public class DataSourceConfig {
    @Bean(name = "masterDataSource")
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties("spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
}

動態(tài)切換數(shù)據(jù)源

spring提供的方案

關(guān)于動態(tài)切換數(shù)據(jù)源,spring給我們提供了一套解決方案,主要通過AbstractRoutingDataSource類實現(xiàn),這個類是一個抽象類,每次和數(shù)據(jù)庫的交互都會調(diào)用該類的getConnection() 方法獲取數(shù)據(jù)庫連接,而getConnection() 方法會調(diào)用determineCurrentLookupKey先選擇一個正確的數(shù)據(jù)源,數(shù)據(jù)源如何選擇呢?他的具體實現(xiàn)是,由我們開發(fā)人員提前將所有的數(shù)據(jù)源通過K-V的格式放到一個map中,V是具體的數(shù)據(jù)源,K是數(shù)據(jù)源的唯一標(biāo)識。然后將這個map交給AbstractRoutingDataSource去管理,在需要路由的時候他會根據(jù)給定的K從map中匹配對應(yīng)的數(shù)據(jù)源。那么K又怎么來呢?哪個接口應(yīng)該用哪個key呢?AbstractRoutingDataSource給我們提供了一個抽象方法determineTargetDataSource(),供我們自定義實現(xiàn)key的確定邏輯。這個其實是對模板方法模式的典型應(yīng)用,核心代碼如下:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    // map結(jié)構(gòu),用來保存所有的數(shù)據(jù)源
	@Nullable
	private Map<Object, Object> targetDataSources;
    // 默認(rèn)的數(shù)據(jù)源
	@Nullable
	private Object defaultTargetDataSource;
	@Override
	public void afterPropertiesSet() {
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
		this.targetDataSources.forEach((key, value) -> {
			Object lookupKey = resolveSpecifiedLookupKey(key);
			DataSource dataSource = resolveSpecifiedDataSource(value);
			this.resolvedDataSources.put(lookupKey, dataSource);
		});
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}
	@Override
	public Connection getConnection() throws SQLException {
		return determineTargetDataSource().getConnection();
	}
	/**
	 * getConnection()方法和determineTargetDataSource()方法定義了獲取數(shù)據(jù)庫連接,選擇數(shù)據(jù)源的核心邏輯
	 */
	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;
	}
	/**
	 * 根據(jù)key選擇數(shù)據(jù)源,但是哪個接口用那個key,由用戶自己決定,這就是模板方法模式
	 */
	@Nullable
	protected abstract Object determineCurrentLookupKey();
}

構(gòu)建動態(tài)數(shù)據(jù)源

在了解了上述的基本原理后,我們就可以著手構(gòu)建我們的動態(tài)數(shù)據(jù)源啦,首先自定義一個類繼承AbstractRoutingDataSource,實現(xiàn)determineCurrentLookupKey()方法。

/**
 * 繼承spring提供的多數(shù)據(jù)源路由類,初始化默認(rèn)數(shù)據(jù)源和實現(xiàn)選擇數(shù)據(jù)源的方法
 *
 * @author HXY
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    // 有參構(gòu)造器,初始化所有的數(shù)據(jù)源和默認(rèn)數(shù)據(jù)源
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> allDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(allDataSource);
        super.afterPropertiesSet();
    }
    // 實現(xiàn)抽象方法,定義我們獲取K的邏輯
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

DataSourceContextHolder類使用ThreadLocal來存儲當(dāng)前線程使用的數(shù)據(jù)源名稱。通過setDataSourceKey()方法設(shè)置數(shù)據(jù)源名稱,通過getDataSourceKey()方法獲取數(shù)據(jù)源名稱,通過clearDataSourceKey()方法清除數(shù)據(jù)源名稱。

這里用ThreadLocal的主要原因是為了做多并發(fā)線程隔離,比如同一時間可能會有很多請求并發(fā)進來,假設(shè)有10個請求,然后系統(tǒng)分配線程1處理請求1,請求1需要用mster數(shù)據(jù)源,線程2處理請求2,請求2需要用slave數(shù)據(jù)源。他們可能同時在進行,那么我們?nèi)绾螌⑦@些請求需要的key做線程隔離呢,使之不互相影響呢?ThreadLocal就可以做到。

public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    public static void setDataSource(String dataSourceKey) {
        CONTEXT_HOLDER.set(dataSourceKey);
    }
    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }
    public static void release() {
        CONTEXT_HOLDER.remove();
    }
}
@Configuration
public class DataSourceConfig {
    @Bean(name = "masterDataSource")
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties("spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
    // DynamicDataSource要交給spring管理
    @Primary  // 一定要寫,讓DynamicDataSource被容器優(yōu)先選擇
    @Bean
    public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        // 所有數(shù)據(jù)源放到一個map中,交給動態(tài)數(shù)據(jù)源管理
        Map<Object, Object> targetDataSources = new HashMap<>(2);
        targetDataSources.put(DataSourceEnum.MASTER.name(), masterDataSource);
        targetDataSources.put(DataSourceEnum.SLAVE.name(), slaveDataSource);
        // 默認(rèn)數(shù)據(jù)源、所有數(shù)據(jù)源
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
}

通過切面將業(yè)務(wù)和數(shù)據(jù)源切換模塊解耦

現(xiàn)在動態(tài)數(shù)據(jù)源切換的方案有了,那么如何能將每一個請求路由的到正確的數(shù)據(jù)源,而且將這些和業(yè)務(wù)無關(guān)的代碼和業(yè)務(wù)進行解耦呢。是的,我們可以用aop,構(gòu)建一個切面,在實現(xiàn)一個自定義注解,將注解標(biāo)記在需要切換數(shù)據(jù)源的接口上,讓每一個請求處理之前先去選擇數(shù)據(jù)源,在處理業(yè)務(wù)邏輯,最后返回結(jié)果是不是就OK了?說干就干

/**
 * 自定義注解用來選擇數(shù)據(jù)源
 *
 * @author HXY
 * @since 1.0.0
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    DataSourceEnum key() default DataSourceEnum.MASTER;
}
public enum DataSourceEnum {
    MASTER,
    SLAVE,
    ;
}
@Aspect
@Component
public class DynamicDataSourceAspect {
    // 用環(huán)繞通知攔截標(biāo)記了DataSource注解的方法,方法執(zhí)行前選擇數(shù)據(jù)源,然后執(zhí)行原來的方法,最后返回結(jié)果
    @Around("@annotation(dataSource)")
    public Object selectDataSource(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {
        try {
            String selectKey = dataSource.key().name();
            DataSourceContextHolder.setDataSource(selectKey);
            return joinPoint.proceed();
        } finally {
            // 請求處理完成后一定要及時釋放ThreadLocal數(shù)據(jù),否則會引起內(nèi)存泄漏
            DataSourceContextHolder.release();
        }
    }
}

到此這篇關(guān)于SpringBoot多數(shù)據(jù)源實現(xiàn)方案的文章就介紹到這了,更多相關(guān)SpringBoot多數(shù)據(jù)源內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java優(yōu)先隊列PriorityQueue中Comparator的用法詳解

    java優(yōu)先隊列PriorityQueue中Comparator的用法詳解

    這篇文章主要介紹了java優(yōu)先隊列PriorityQueue中Comparator的用法詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • Java布隆過濾器的應(yīng)用實例

    Java布隆過濾器的應(yīng)用實例

    這篇文章主要介紹了Java布隆過濾器的應(yīng)用實例,在程序的世界中,布隆過濾器是程序員的一把利器,利用它可以快速地解決項目中一些比較棘手的問題,如網(wǎng)頁?URL?去重、垃圾郵件識別、大集合中重復(fù)元素的判斷和緩存穿透等問題,需要的朋友可以參考下
    2023-11-11
  • Spring?Boot整合Kafka教程詳解

    Spring?Boot整合Kafka教程詳解

    這篇文章主要為大家介紹了Spring?Boot整合Kafka教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • Java接口回調(diào)的本質(zhì)詳解

    Java接口回調(diào)的本質(zhì)詳解

    大家好,本篇文章主要講的是Java接口回調(diào)的本質(zhì)詳解,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-02-02
  • IDEA使用JDBC安裝配置jar包連接MySQL數(shù)據(jù)庫

    IDEA使用JDBC安裝配置jar包連接MySQL數(shù)據(jù)庫

    這篇文章介紹了IDEA使用JDBC安裝配置jar包連接MySQL數(shù)據(jù)庫的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-01-01
  • 最新評論

    运城市| 遂川县| 平陆县| 永定县| 彩票| 广灵县| 丰镇市| 昌图县| 克山县| 盈江县| 延川县| 莎车县| 远安县| 礼泉县| 巴里| 沂源县| 深圳市| 卢氏县| 高要市| 玉树县| 连云港市| 栾川县| 屯门区| 阿拉善左旗| 鄱阳县| 连城县| 永修县| 乐平市| 伊吾县| 孟村| 精河县| 汤原县| 上栗县| 莒南县| 浮梁县| 新民市| 凌云县| 渭南市| 虞城县| 昌江| 虹口区|