根據不同域名實現數據源切換
最近在做項目合並,之前排隊項目(子項目)從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
根據不同域名實現數據源切換