mybati-plus切換數據源的兩種方式


在工作中我們在一個springboot項目中,經常會有不同數據源的場景,如果我們項目中用的是mybatis-plus,那就很方便去配置,如果用的是mybatis可以切換成mybatis-plus,也可以自己實現一個動態切換數據源(spring已經給我們提供了一個接口)

首先引入依賴

<!--    mybatis-plus    -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>

一.一般使用情況下使用@DS("數據源名稱"),我們進行切換數據源

@DS("slave")
public List<KeyValueVo> list(String areaCode) {
    List<KeyValueVo> list = slaveMapper.list(areaCode);
    return list;
}

只需要在application.yml添加一組dataSource就可以了

dynamic:
  primary: master 
  datasource:
  
    master:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/master
      username: root
      password: 123456

    slave:
      url: jdbc:mysql://127.0.0.1:3306/slave
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver

二.特殊情況@DS("")不能滿足我們的所有需求,比如我們在slave數據源的service方法中要使用master中的數據,例如:我數據在slave字典在master中,於是我寫成了以下這樣(這樣寫是錯誤的,並不能達到我們想要的效果)

@DS("slave")
public List<KeyValueVo> list(String areaCode) {
    List<KeyValueVo> list = slaveMapper.list(areaCode);
    List<Dict> educationes = listDict("education");
    ...后續操作...
}

@DS("master")
public List<Dict> listDict(String dictType) {
    List<Dict> list = dictMapper.list(dictType);
    return list;
}

原因:mybatis-plus數據源是利用spring-aop實現的,對於aop而言它是以每次請求為單位的,簡單的說,雖然我們使用了兩個方法,分別配置了兩個@DS("),但其實第二個並不會生效。

解決辦法,對於一次請求兩個數據源在把第二個數據源改為手動修改,下面的手動修改配置:

public List<Dict> listDict(String dictType) {
    DynamicDataSourceContextHolder.push("master");
    List<Dict> list = dictMapper.list(dictType);
    DynamicDataSourceContextHolder.poll();
    return list;
}

原理:我們看下源碼,找到mybatis-plus中DynamicRoutingDataSource類:

public DataSource determineDataSource() {
    return this.getDataSource(DynamicDataSourceContextHolder.peek());
}

public DataSource getDataSource(String ds) {
    if (StringUtils.isEmpty(ds)) {
        return this.determinePrimaryDataSource();
    } else if (!this.groupDataSources.isEmpty() && this.groupDataSources.containsKey(ds)) {
        log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
        return ((DynamicGroupDataSource) this.groupDataSources.get(ds)).determineDataSource();
    } else if (this.dataSourceMap.containsKey(ds)) {
        log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
        return (DataSource) this.dataSourceMap.get(ds);
    } else if (this.strict) {
        throw new RuntimeException("dynamic-datasource could not find a datasource named" + ds);
    } else {
        return this.determinePrimaryDataSource();
    }
}

主要有兩個方法,determineDataSource()會在使用數據源時調用,determineDataSource()方法中使用DynamicDataSourceContextHolder.peek()獲得一個字符串調用getDataSource()獲取數據源:

private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
    protected Object initialValue() {
        return new ArrayDeque();
    }
};

private DynamicDataSourceContextHolder() {
}

public static String peek() {
    return (String)((Deque)LOOKUP_KEY_HOLDER.get()).peek();
}

public static void push(String ds) {
    ((Deque)LOOKUP_KEY_HOLDER.get()).push(StringUtils.isEmpty(ds) ? "" : ds);
}

public static void poll() {
    Deque<String> deque = (Deque)LOOKUP_KEY_HOLDER.get();
    deque.poll();
    if (deque.isEmpty()) {
        LOOKUP_KEY_HOLDER.remove();
    }

}

在DynamicDataSourceContextHolder中定義了一個雙端隊列LOOKUP_KEY_HOLDER,它的peek()方法每次取得時當前線程的首個,所以我們在手動卻換的過程中,加入一個我們想要的datasource然后通過poll()方法刪除就能達到我們手動切換的目的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM