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

項目中需要使用南大信通數(shù)據(jù)庫GBase,因此配置多數(shù)據(jù)源用以查詢不同數(shù)據(jù)庫的數(shù)據(jù).先做一個小Demo測試數(shù)據(jù)源動態(tài)切換钠糊。主要使用切面修改配置文件實現(xiàn)數(shù)據(jù)源切換垫言,再切點后置通知中再切回主數(shù)據(jù)源。

引入依賴

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 阿里的數(shù)據(jù)庫連接池依賴 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.4</version>
        </dependency>
        <!--lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
        <!--spring aop-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>

配置文件

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    ds:
      master:
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      slave:
        url: jdbc:mysql://localhost:3306/datasource?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
    initialSize: 10
    minIdle: 10
    maxActive: 100
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 2000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: false
    maxPoolPreparedStatementPerConnectionSize: 0
    filters: stat,log4j
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    useGlobalDataSourceStat: true
server:
  port: 8081
  servlet:
    context-path: /


mybatis:
  type-aliases-package: com.data.datasource.entity
  mapper-locations: classpath:mybatis/**/**.xml
  # ?????????
#  configLocation: classpath:mybatis/mybatis-config.xml

自定義注解類

/**
 * 多數(shù)據(jù)源注解
 * (這個注解可以加在某一個 service類上或者方法上瞻讽,通過 value 屬性來指定類或者方法應(yīng)該使用哪一個數(shù)據(jù)源)
 * @author 
 * @since 2022-08-10
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
    /**
     * 數(shù)據(jù)源名稱
     * (如果一個方法上加了 @DataSource 注解鸳吸,但是沒有指定數(shù)據(jù)源名稱,默認(rèn)使用 master 數(shù)據(jù)源)
     * @return
     */
    String value() default DataSourceType.DEFAULT_DS_NAME;

多數(shù)據(jù)源切面類

/**
 * 注解@DataSource切面
 * @author 
 * @since 2022-08-10
 */
@Component
@Aspect
@Order(10)
public class DataSourceAspect {

    /**
     * 定義切點
     * @annotation(cn.lzy.study.datasource.annotation.DataSource)--表示方法上有注解 @DataSource 就將方法攔截下來
     * @within(cn.lzy.study.datasource.annotation.DataSource)--表示如果類上面有注解 @DataSource 就將該類中的方法攔截下來
     */
    @Pointcut("@annotation(com.data.datasource.annotation.DataSource) || @within(com.data.datasource.annotation.DataSource)")
    public void pc() {

    }


    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp) {
        // 獲取方法上的有效注解
        DataSource dataSouce = getDataSouce(pjp);
        if (dataSouce != null) {
            // 獲取注解中的值(即:數(shù)據(jù)源名稱)
            String dataSourceName = dataSouce.value();
            // 將該數(shù)據(jù)源名稱設(shè)置到線程中
            DynamicDataSourceContextHolder.setDataSourceType(dataSourceName);
        }
        try {
            // 放行方法(執(zhí)行真實方法)
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } finally {
            // 清除數(shù)據(jù)源
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
        return null;
    }


    /**
     * 獲取方法上的注解對象
     * @param pjp
     * @return
     */
    private DataSource getDataSouce(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 查找方法上的注解
        DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if (annotation != null) {   // 方法上存在注解
            return annotation;
        }
        // 方法上不存在注解速勇,則返回類上的注解(signature.getDeclaringType()--獲取方法所在的類型)
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

全局多數(shù)據(jù)源切面類

/**
 * 全局?jǐn)?shù)據(jù)源切面
 * @author 
 * @since 2022-08-13
 */
@Component
@Aspect
// 設(shè)置優(yōu)先級(執(zhí)行順序)晌砾,數(shù)字越小,優(yōu)先級越高
@Order(9)
public class GloalDataSourceAspect {

    @Autowired
    private HttpSession httpSession;

    // 攔截切點:所有service層的任意類任意方法
    @Pointcut("execution(* com.data.datasource.service.*.*(..))")
    public void pc() {

    }


    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp) {

        DynamicDataSourceContextHolder.setDataSourceType((String) httpSession.getAttribute(DataSourceType.DS_SESSION_KEY));
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
        return null;
    }
}

數(shù)據(jù)源對象

/**
 * 數(shù)據(jù)源對象
 * (從配置文件獲取數(shù)據(jù)源值)
 * @author 
 * @since 2022-08-10
 */
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
    /**
     * 數(shù)據(jù)池類型
     */
    private String type;
    /**
     * 數(shù)據(jù)庫驅(qū)動
     */
    private String driverClassName;
    /**
     * 數(shù)據(jù)源
     */
    private Map<String, Map<String, String>> ds;
    /**
     * 初始連接數(shù)
     */
    private Integer initialSize;
    /**
     * 最小連接池數(shù)量
     */
    private Integer minIdle;
    /**
     * 最大連接池數(shù)量
     */
    private Integer maxActive;
    /**
     * 配置獲取連接等待超時的時間
     */
    private Integer maxWait;


    /**
     * 給數(shù)據(jù)源初始化數(shù)據(jù)
     * (在這個方法里烦磁,給數(shù)據(jù)源設(shè)置公共屬性)
     * @param druidDataSource 數(shù)據(jù)源养匈,但是這個數(shù)據(jù)源對象只包含 url、username都伪、password 三個核心屬性
     * @return
     */
    public DataSource initDataSource(DruidDataSource druidDataSource) {
        druidDataSource.setInitialSize(this.minIdle);
        druidDataSource.setMinIdle(this.minIdle);
        druidDataSource.setMaxActive(this.maxActive);
        druidDataSource.setMaxWait(this.maxWait);
        return druidDataSource;
    }

    // ====================== Getter & Setter ========================

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public Map<String, Map<String, String>> getDs() {
        return ds;
    }

    public void setDs(Map<String, Map<String, String>> ds) {
        this.ds = ds;
    }

    public Integer getInitialSize() {
        return initialSize;
    }

    public void setInitialSize(Integer initialSize) {
        this.initialSize = initialSize;
    }

    public Integer getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(Integer minIdle) {
        this.minIdle = minIdle;
    }

    public Integer getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(Integer maxActive) {
        this.maxActive = maxActive;
    }

    public Integer getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(Integer maxWait) {
        this.maxWait = maxWait;
    }
}

數(shù)據(jù)源類型類

public interface DataSourceType {
    String DEFAULT_DS_NAME = "master";
    String DS_SESSION_KEY = "ds_session_key";
}

動態(tài)數(shù)據(jù)源核心實現(xiàn)類


/**
 * 【動態(tài)多數(shù)據(jù)源切換功能實現(xiàn)-核心點】
 * 當(dāng) mapper 需要獲取數(shù)據(jù)源時呕乎,會去 AbstractRoutingDataSource 類中進(jìn)行處理
 * @author 
 * @since 2022-08-11
 */
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 該方法用來返回數(shù)據(jù)源名稱
     * (當(dāng)系統(tǒng)需要獲取數(shù)據(jù)源的時候,會自動調(diào)用該方法獲取數(shù)據(jù)源的名稱)
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }



    public DynamicDataSource(LoadDataSource loadDataSource) {
        // 1.設(shè)置所有的數(shù)據(jù)源
        Map<String, DataSource> allDataSourceMap = loadDataSource.loadAllDataSource();
        super.setTargetDataSources(new HashMap<>(allDataSourceMap));

        // 2.設(shè)置默認(rèn)數(shù)據(jù)源(作用:并不是所有的方法上都有 @DataSource 注解陨晶,對于那些沒有 @DataSource 注解的方法猬仁,設(shè)置使用默認(rèn)數(shù)據(jù)源)
        super.setDefaultTargetDataSource(allDataSourceMap.get(com.data.datasource.datasource.DataSourceType.DEFAULT_DS_NAME));

        super.afterPropertiesSet();
    }
}

數(shù)據(jù)源存儲類

/**
 * 這個類用來存儲當(dāng)前線程所使用的數(shù)據(jù)源名稱
 * @author 
 * @since 2022-08-10
 */
public class DynamicDataSourceContextHolder {

    private static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 設(shè)置數(shù)據(jù)源
     * @param dataSourceType    數(shù)據(jù)源名稱
     */
    public static void setDataSourceType(String dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }

    /**
     * 獲取數(shù)據(jù)源
     * @return  數(shù)據(jù)源名稱
     */
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }


    /**
     * 清空數(shù)據(jù)源(防止內(nèi)存溢出、泄露)
     */
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

數(shù)據(jù)源加載類


/**
 * 加載數(shù)據(jù)源
 * @author 
 * @since 2022-08-11
 */
@Component
@EnableConfigurationProperties(DruidProperties.class)   // 引入該配置屬性
public class LoadDataSource {

    @Autowired
    private DruidProperties druidProperties;

    /**
     * 加載所有數(shù)據(jù)源
     * @return
     */
    public Map<String, DataSource> loadAllDataSource() {
        // 所有的數(shù)據(jù)源緩存
        Map<String, DataSource> map = new HashMap<>();

        // 從配置屬性中獲取數(shù)據(jù)源信息
        Map<String, Map<String, String>> ds = druidProperties.getDs();
        try {
            for(String dataSourceName : ds.keySet()) {
                // 根據(jù)配置屬性信息創(chuàng)建數(shù)據(jù)源(得到的數(shù)據(jù)源僅有三個核心屬性:url先誉、username湿刽、password)
                DataSource baseDataSource = DruidDataSourceFactory.createDataSource(ds.get(dataSourceName));
                // 對獲取到的數(shù)據(jù)源,進(jìn)行公共屬性賦值
                DataSource dataSource = druidProperties.initDataSource((DruidDataSource) baseDataSource);
                // 將數(shù)據(jù)源存入到緩存中
                map.put(dataSourceName, dataSource);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return map;
    }
}

兩個數(shù)據(jù)源


image.png

image.png

接口調(diào)用


image.png

注解織入
image.png

image.png

調(diào)用成功案例


image.png

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谆膳,一起剝皮案震驚了整個濱河市叭爱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌漱病,老刑警劉巖买雾,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件把曼,死亡現(xiàn)場離奇詭異,居然都是意外死亡漓穿,警方通過查閱死者的電腦和手機嗤军,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晃危,“玉大人叙赚,你說我怎么就攤上這事×欧梗” “怎么了震叮?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鳍鸵。 經(jīng)常有香客問我苇瓣,道長,這世上最難降的妖魔是什么偿乖? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任击罪,我火速辦了婚禮,結(jié)果婚禮上贪薪,老公的妹妹穿的比我還像新娘媳禁。我一直安慰自己,他們只是感情好画切,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布竣稽。 她就那樣靜靜地躺著,像睡著了一般霍弹。 火紅的嫁衣襯著肌膚如雪丧枪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天庞萍,我揣著相機與錄音,去河邊找鬼忘闻。 笑死钝计,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的齐佳。 我是一名探鬼主播私恬,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炼吴!你這毒婦竟也來了本鸣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤硅蹦,失蹤者是張志新(化名)和其女友劉穎荣德,沒想到半個月后闷煤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡涮瞻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年鲤拿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片署咽。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡近顷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窒升,到底是詐尸還是另有隱情慕匠,我是刑警寧澤絮重,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布青伤,位于F島的核電站,受9級特大地震影響号杠,放射性物質(zhì)發(fā)生泄漏姨蟋。R本人自食惡果不足惜立帖,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一晓勇、第九天 我趴在偏房一處隱蔽的房頂上張望绑咱。 院中可真熱鬧,春花似錦描融、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丽啡。三九已至硬猫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坑雅,已是汗流浹背衬横。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工蜂林, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留噪叙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像瀑凝,于是被迫代替她去往敵國和親臭杰。 傳聞我的和親對象是個殘疾皇子渴杆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

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