Spring多資料來源、動態資料源源碼解析

來源:互聯網
上載者:User

標籤:ssl   tar   複雜   oca   throws   div   ali   resolved   stack   

在Java中所有的串連池都按照規範實現DataSource介面,在擷取串連的時候即可通過getConnection()擷取串連而不用關係底層究竟是何資料庫連接池。


1 public interface DataSource  extends CommonDataSource, Wrapper {2 3   Connection getConnection() throws SQLException;4 5   Connection getConnection(String username, String password) throws SQLException;6 }

 

在大多數系統中我們只需要一個資料來源,而現在WEB系統通常是Spring為基石。不管你是xml配置,javaBean配置還是yml,properties設定檔配置,其核心就是注入一個資料來源交給spring的進行管理。

而在部分系統中我們可能會面臨一些情況,串連多個表,主從,甚至多個不同的庫等等情況,核心需求就是我們可能需要配置多個串連池。

在mybatis系統中我們使用多資料來源可以配置配置多個DataSource,SqlSessionFactory,SqlSessionTemplate,然後在xml和mapper也分開管理。具體可以參考61202084 這篇部落格。

這種方案在小的系統足夠使用,作者認為更適合於多個不同的資料庫。

迴歸正題,在Spring中從2.0.1版本預設提供了AbstractRoutingDataSource,我們繼承它實現相關方法,把所有需要的資料來源設定進去即可動態切換資料來源。我們可以看下核心方法的源碼。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {?    //設定所有的資料來源    private Map<Object, Object> targetDataSources;    //設定預設的資料來源,在沒有找到相關資料來源的時候會返回預設資料來源    private Object defaultTargetDataSource;    //快速失敗,可忽略    private boolean lenientFallback = true;    //Jndi相關,可忽略    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();    //經過解析後的所有資料來源,核心    private Map<Object, DataSource> resolvedDataSources;    //經過解析後的預設資料來源,核心    private DataSource resolvedDefaultDataSource;?    //設定相關參數方法    public void setTargetDataSources(Map<Object, Object> targetDataSources) {        this.targetDataSources = targetDataSources;    }    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {        this.defaultTargetDataSource = defaultTargetDataSource;    }    public void setLenientFallback(boolean lenientFallback) {        this.lenientFallback = lenientFallback;    }    public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {        this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());    }?    @Override    public void afterPropertiesSet() {        //檢測是否設定所有的資料來源        if (this.targetDataSources == null) {            throw new IllegalArgumentException("Property ‘targetDataSources‘ is required");        }        //解析所有資料來源,一般沒什麼用,主要是如果Map<Object, Object> targetDataSources的value是string則會從Jndi資料來源尋找        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);        }    }?    @Override    public Connection getConnection() throws SQLException {        //核心,擷取資料來源先尋找當前串連池再擷取資料來源        return determineTargetDataSource().getConnection();    }?    @Override    public Connection getConnection(String username, String password) throws SQLException {        return determineTargetDataSource().getConnection(username, password);    }?    protected DataSource determineTargetDataSource() {        //調用determineCurrentLookupKey,然後去resolvedDefaultDataSource尋找,有就返回對應資料來源,沒有返回預設資料來源        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;    }        //而determineCurrentLookupKey需要我們自己去實現。    //通常需要結合Aop和ThreadLocal。我們Aop從註解上擷取目前使用者使用者所希望的資料來源,然後設定到當前線程。在determineCurrentLookupKey再從當前線程拿出來返回給determineTargetDataSource由其決定最終資料來源    protected abstract Object determineCurrentLookupKey();?}

  

以上具體的實現可以參考這篇部落格 77449710 需要注意的是AOP的order必須在事物的order之前。

優點:方便配置,便捷使用。缺點:預設實現有一定局限性,大多數人足夠使用。如果你有更複雜的使用情境,多庫資料來源,分組資料來源,多主多從等等較複雜情境可以嘗試

https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter

一個基於springboot的快速整合多資料來源的啟動器。

一個標準的主從的配置如下,引入相關配置即可使用。更多使用的方式查看相關文檔。


spring:  datasource:    dynamic:      primary: master #設定預設的資料來源或者資料來源組,預設值即為master,如果你主從預設下主庫的名稱就是master可不定義此項。      datasource:        master:          username: root          password: 123456          driver-class-name: com.mysql.jdbc.Driver          url: jdbc:mysql://47.100.20.186:3306/dynamic?characterEncoding=utf8&useSSL=false        slave_1:          username: root          password: 123456          driver-class-name: com.mysql.jdbc.Driver          url: jdbc:mysql://47.100.20.186:3307/dynamic?characterEncoding=utf8&useSSL=false        slave_2:          username: root          password: 123456          driver-class-name: com.mysql.jdbc.Driver          url: jdbc:mysql://47.100.20.186:3308/dynamic?characterEncoding=utf8&useSSL=false

  

實現核心源碼如下

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {    /**     * 所有庫     */    private Map<String, DataSource> dataSourceMap;    /**     * 分組資料庫     */    private Map<String, DynamicGroupDatasource> groupDataSources = new HashMap<>();    @Setter    private DynamicDataSourceProvider dynamicDataSourceProvider;    @Setter    private Class<? extends DynamicDataSourceStrategy> dynamicDataSourceStrategyClass;    /**     * 預設資料來源名稱,預設master,可為組資料來源名,可為單資料來源名     */    @Setter    private String primary;    @Override    protected Object determineCurrentLookupKey() {        return DynamicDataSourceContextHolder.getDataSourceLookupKey();    }    @Override    protected DataSource determineTargetDataSource() {        String lookupKey = (String) determineCurrentLookupKey();        if (groupDataSources.containsKey(lookupKey)) {            log.debug("從 {} 組資料來源中返回資料來源", lookupKey);            return groupDataSources.get(lookupKey).determineDataSource();        } else if (dataSourceMap.containsKey(lookupKey)) {            log.debug("從 {} 單資料來源中返回資料來源", lookupKey);            return dataSourceMap.get(lookupKey);        }        log.debug("從預設資料來源中返回資料");        return groupDataSources.containsKey(primary) ? groupDataSources.get(lookupKey).determineDataSource() : dataSourceMap.get(primary);    }    @Override    public void afterPropertiesSet() {        this.dataSourceMap = dynamicDataSourceProvider.loadDataSources();        log.debug("共載入 {} 個資料來源", dataSourceMap.size());        //分組資料來源        for (Map.Entry<String, DataSource> dsItem : dataSourceMap.entrySet()) {            String dsName = dsItem.getKey();            if (dsName.contains("_")) {                String[] groupDs = dsName.split("_");                String groupName = groupDs[0];                DataSource dataSource = dsItem.getValue();                if (groupDataSources.containsKey(groupName)) {                    groupDataSources.get(groupName).addDatasource(dataSource);                } else {                    try {                        DynamicGroupDatasource groupDatasource = new DynamicGroupDatasource(groupName, dynamicDataSourceStrategyClass.newInstance());                        groupDatasource.addDatasource(dataSource);                        groupDataSources.put(groupName, groupDatasource);                    } catch (Exception e) {                        e.printStackTrace();                    }                }            }        }        //檢測組資料來源設定        Iterator<Map.Entry<String, DynamicGroupDatasource>> groupIterator = groupDataSources.entrySet().iterator();        while (groupIterator.hasNext()) {            Map.Entry<String, DynamicGroupDatasource> item = groupIterator.next();            log.debug("組 {} 下有 {} 個資料來源", item.getKey(), item.getValue().size());            if (item.getValue().size() == 1) {                log.warn("請注意不要設定一個只有一個資料來源的組,{} 組將被移除", item.getKey());                groupIterator.remove();            }        }        //檢測預設資料來源設定        if (groupDataSources.containsKey(primary)) {            log.debug("當前的預設資料來源是組資料來源,組名為 {} ,其下有 {} 個資料來源", primary, groupDataSources.size());        } else if (dataSourceMap.containsKey(primary)) {            log.debug("當前的預設資料來源是單資料來源,資料來源名為{}", primary);        } else {            throw new RuntimeException("請檢查primary預設資料庫設定,當前未找到" + primary + "資料來源");        }    }}

  

Spring多資料來源、動態資料源源碼解析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.