最近在做项目合并,之前排队项目(子项目)从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的方法。
@Overridepublic 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
转载于:https://www.cnblogs.com/xlblog/p/9835976.html