最近在做项目合并,之前排队项目(子项目)从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