• 根据不同域名实现数据源切换


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

  • 相关阅读:
    SPOJ NDIV
    SPOJ ETF
    SPOJ DIVSUM
    头文件--持续更新
    SPOJ FRQPRIME
    SPOJ FUNPROB
    SPOJ HAMSTER1
    观光
    最短路计数
    拯救大兵瑞恩
  • 原文地址:https://www.cnblogs.com/xlblog/p/9835976.html
Copyright © 2020-2023  润新知