標籤:url context thold 分系統 prim 規範 .data comm 失敗
在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也分開管理。
這種方案在小的系統足夠使用,作者認為更適合於多個不同的資料庫。
迴歸正題,在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());br/>}
?
@Override
//檢測是否設定所有的資料來源
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);br/>}
}
?
@Override
//核心,擷取資料來源先尋找當前串連池再擷取資料來源
return determineTargetDataSource().getConnection();br/>}
?
@Override
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();
?
}
優點:方便配置,便捷使用。 缺點:預設實現有一定局限性,大多數人足夠使用。 如果你有更複雜的使用情境,多庫資料來源,分組資料來源,多主多從等等較複雜情境可以嘗試
一個基於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<>();@Setterprivate DynamicDataSourceProvider dynamicDataSourceProvider;@Setterprivate Class<? extends DynamicDataSourceStrategy> dynamicDataSourceStrategyClass;/** * 預設資料來源名稱,預設master,可為組資料來源名,可為單資料來源名 */@Setterprivate String primary;@Overrideprotected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceLookupKey();}@Overrideprotected 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);}@Overridepublic 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多資料來源、動態資料源源碼解析