1. 程式人生 > >根據不同域名實現數據源切換

根據不同域名實現數據源切換

defs exclude gets ins else 數據庫切換 esp super local

最近在做項目合並,之前排隊項目(子項目)從idm項目(父項目)分開的,考慮的是獨立開發,但開發到後面太多依賴idm這邊,所以現在又要合並。。。。

子項目這邊有個saas模塊,主要是根據不同域名實現訪問不同數據庫,主要用到的是域名攔截器+spring數據源切換(AbstractRoutingDataSource)。

1:數據庫這裏分為子庫和主庫,子庫存放的就是業務數據,主庫存放管理子庫信息。

主要是兩張表

數據庫連接信息

技術分享圖片

域名和數據庫連接信息對應關系,這裏dbconnid對應就是上漲表的id

技術分享圖片

這樣多數據庫的消息就保存在主庫了。

2域名攔截器

普通攔截器,主要對所有請求攔截2級域名,並保存到一個基礎類ThreadLocalUtil中。

例如:http://xl.test.com/test 這裏獲取的是xl這個2級域名,保存到基礎類ThreadLocalUtil,方便後面根據這個域名連接數據庫。

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
String serverName = request.getServerName();
String sec = "";
if (IpUtil.isIp(serverName)) { //這裏如果是ip默認主庫
sec = IConstant.WWW;
} else {
sec = DomainUtil.getSecond(serverName);
}
List<String> allDomain = DomainUtil.getAllDomain();
if (allDomain.contains(sec)) {
ThreadLocalUtil.setSecDomain(sec);
if (ThreadLocalUtil.isDefSecDomain()) {
log.debug("默認域名{},url={}", sec, serverName);
}
chain.doFilter(request, response);
} else {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String reqUri = req.getRequestURI();
for (String exclude : excludes) {
if (reqUri.contains(exclude)) {
chain.doFilter(request, response);
return;
}
}
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
resp.sendRedirect(req.getContextPath() + "/error/404.jsp");
}
} finally {
ThreadLocalUtil.delSecDomain();
}
}

3初始化方法

這裏有個初始化方法,將數據庫所有saas的數據獲取,並add到MultiDataSource中,addDataSource 這裏傳入的map,key域名,value數據庫信息

技術分享圖片

4:關鍵的MultiDataSource,這個類繼承了AbstractRoutingDataSource。

而關於AbstractRoutingDataSource這個類也就是數據源切換的關鍵。

該類是根據這個方法確定連接那個數據源的,而關鍵就是lookupKey 對象,也就是這個方法determineCurrentLookupKey,而該類的這個方法是抽象,具體實現由子類決定;其次這裏是從

resolvedDataSources這個map中取的,這裏當然有set這個map的方法,不過不是直接的,是間接的

源:

類中的map變量

private Map<Object, Object> targetDataSources;

private Map<Object, DataSource> resolvedDataSources;

確定連接哪個數據源的方法

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;
}

抽象方法

protected abstract Object determineCurrentLookupKey();

set resolvedDataSources的方法,通過targetDataSources這個map。這裏有直接set targetDataSources的方法。

@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property ‘targetDataSources‘ is required");
}
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);
}
}

set targetDataSources方法

public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}

我們這裏實現是這樣的。

這裏通過前面域名攔截器存放的域名,來決定連接哪個數據庫。

@Override
protected Object determineCurrentLookupKey() {
String secDomain=ThreadLocalUtil.getSecDomain();前面存的這裏拿出來。
if(!IConstant.WWW.equals(secDomain)){
return secDomain;
}else{
log.debug("默認域名{}",secDomain);
}
return def;
}

我們這裏也肯定調用了它的set方法

public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
super.setTargetDataSources(targetDataSources); 調用父類的set方法。
TDSversion++;
}

初始化的時候調用的方法


/**
* 增加多個數據庫連接池
*
* @param conns
*/
public void addDataSource(Map<String, DbConnInfo> conns) {
if (null != conns && conns.size() > 0) {
LinkedHashMap<Object, Object> newTargetDataSources = new LinkedHashMap<>(targetDataSources);
Set<String> keys = conns.keySet();
for (String key : keys) {
DbConnInfo conn = conns.get(key);
DataSource ds = buildDataSource(conn);
newTargetDataSources.put(key, ds);
domainDbConnMap.put(key, conn);
}
setTargetDataSources(newTargetDataSources); //這裏會將key為域名value為數據庫連接信息的map,set到targetdatasources中
afterPropertiesSet();  //這個方法在變更連接信息後需要調用。
}
}

這樣實現根據域名實現數據庫切換。

0系統初始化-——1用戶請求——2域名攔截器——3切換數據源。

寫的有點亂。。

關於切換數據源,可以參考這個地址寫的很仔細。

https://blog.csdn.net/yizhenn/article/details/53965552

根據不同域名實現數據源切換