动态切换数据库源码解析

动态切库可用于SaaS环境,多租户环境

所以浏览器的每次请求都有可能是不同租户,需要动态切换数据库来支持业务场景。
又所以每次请求都需要识别是哪个租户,这里我们用到了ThreadLocal,以此来保存线程的本地变量,携带上租户的一些信息。而租户的信息可以从Session或Token中获取,或者是url路径参数

准备好以上条件信息 我们开始秀吧

创建 DruidDynamicDataSource 继承自 AbstractRoutingDataSource

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

重写determineCurrentLookupKey方法即可 往下看具体操作

此类可以在执行数据库操作之前确定需要切换到哪个库

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author dadiwm321
 */
public class DruidDynamicDataSource extends AbstractRoutingDataSource {

    private Logger logger = LogManager.getLogger(getClass());

    //这里存放了每个租户数据源,如果没有则创建数据源 我们在配置中以为它put了默认数据源,看下面代码可知
    public Map<Object, Object> targetDataSources = new HashMap();

    private static final String mysql = "mysql";
    private static final String oracle = "oracle";

    @Override
    protected Object determineCurrentLookupKey() {
        //这里便是使用了ThreadLocal 实现每个线程独立保存本地副本,明确线程的租户信息
        String dataSourceName = DataContextHolder.getCurrentDataSourceName();
        if (dataSourceName == null) {
            logger.info("==================== change DB:<defaultDataSource> ==================== ");
            //如果为null则返回默认的数据库链接
            return "defaultDataSource";
        }
        Object obj = targetDataSources.get(dataSourceName);
        if (obj == null) {//没有则新建
            DataSource dataSource = null;
            DbInfo dbinfo = DataContextHolder.getCurrentDBInfo();
            if (dbinfo != null && StringUtils.isNotBlank(dbinfo.getDbName())) {
                String url;
                if (mysql.equals(dbinfo.getDbType())) {
                    url = "jdbc:mysql://" + dbinfo.getDomainName() + ":" + dbinfo.getPort() + "/" + dbinfo.getDbName() + "?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false";

                } else if (oracle.equals(dbinfo.getDbType())) {
                    url = "jdbc:oracle:thin:@" + dbinfo.getDomainName() + ":" + dbinfo.getPort() + ":" + dbinfo.getDbName();
                    dbinfo.setUrl(url);
                } else {
                    logger.error("不支持的数据库类型:" + dbinfo.getDbType());
                    throw new RuntimeException("不支持的数据库类型:" + dbinfo.getDbType());
                }
                dbinfo.setUrl(url);

                dataSource = createDataSource(dbinfo);
            }

            if (null != dataSource) {
                targetDataSources.put(dataSourceName, dataSource);
                setTargetDataSources(targetDataSources);
                afterPropertiesSet();
                DataContextHolder.setDataSourceType(dataSourceName);
            }
        }
        logger.info("==================== change DB:<{}> ==================== ", dataSourceName);
        return DataContextHolder.getCurrentDataSourceName();//返回当前需要的数据源
    }

    public DruidDataSource createDataSource(DbInfo dbInfo) {
        DruidDataSource parent = (DruidDataSource) targetDataSources.get("defaultDataSource");
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(dbInfo.getUrl());
        dataSource.setUsername(dbInfo.getLoginName());
        dataSource.setPassword(dbInfo.getLoginPw());

        dataSource.setDriverClassName(dbInfo.getClassDriverName());

        dataSource.setMaxActive(parent.getMaxActive());
        dataSource.setMinIdle(parent.getMinIdle());
        dataSource.setInitialSize(parent.getInitialSize());
        dataSource.setMaxWait(parent.getMaxWait());
        dataSource.setTimeBetweenEvictionRunsMillis(parent.getTimeBetweenEvictionRunsMillis());
        dataSource.setMinEvictableIdleTimeMillis(parent.getMinEvictableIdleTimeMillis());
        dataSource.setValidationQuery(parent.getValidationQuery());
        dataSource.setBreakAfterAcquireFailure(parent.isBreakAfterAcquireFailure());
        dataSource.setConnectionErrorRetryAttempts(parent.getConnectionErrorRetryAttempts());
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);
        dataSource.setDbType(dbInfo.getDbType());
        return dataSource;
    }
}

以上术语可能不太准确,希望大神不吝赐教

原文地址:https://www.cnblogs.com/dadiwm321/p/dynamic_datasource.html

时间: 2024-10-29 00:41:51

动态切换数据库源码解析的相关文章

Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? ??如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一样,本篇文章最最核心的要点就是 SqlSession实现数据库操作的源码解析.但按照惯例,我这边依然列出如下的问题: 1. SqlSession 是如何被创建的? 每次的数据库操作都会创建一个新的SqlSession么?(也许有很多同学会说SqlSession是通过 SqlSessionFactor

Android xUtils3源码解析之数据库模块

xUtils3源码解析系列 一. Android xUtils3源码解析之网络模块 二. Android xUtils3源码解析之图片模块 三. Android xUtils3源码解析之注解模块 四. Android xUtils3源码解析之数据库模块 配置数据库 DbManager.DaoConfig daoConfig = new DbManager.DaoConfig() .setDbName("test.db") .setDbVersion(1) .setDbOpenListe

JDK1.8 动态代理机制及源码解析

动态代理 a) jdk 动态代理 Proxy, 核心思想:通过实现被代理类的所有接口,生成一个字节码文件后构造一个代理对象,通过持有反射构造被代理类的一个实例,再通过invoke反射调用被代理类实例的方法,来实现代理. 缺点:被代理类必须实现一个或多个接口 参考链接:http://rejoy.iteye.com/blog/1627405 源码解析:见第四部分 cglib 动态代理 核心思想:通过生成子类字节码实现,代理类为每个委托方法都生成两个方法,以add方法为例,一个是重写的add方法,一个

时序数据库 Apache-IoTDB 源码解析之文件数据块(四)

上一章聊到行式存储.列式存储的基本概念,并介绍了 TsFile 是如何存储数据以及基本概念.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件格式简介(三) 打一波广告,欢迎大家访问IoTDB 仓库,求一波 Star .欢迎关注头条号:列炮缓开局,欢迎关注OSCHINA博客 这一章主要想聊一聊: TsFile的文件概览 TsFile的数据块 TsFile文件概览 一个完整的 TsFile 是由图中的几大块组成,图中的数据块与索引块之间使用 1 个字节的分隔符 2 来进行分隔,这个分

时序数据库 Apache-IoTDB 源码解析之文件索引块(五)

上一章聊到 TsFile 的文件组成,以及数据块的详细介绍.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件数据块(四) 打一波广告,欢迎大家访问IoTDB 仓库,求一波 Star. 这一章主要想聊聊: TsFile索引块的组成 索引块的查询过程 索引块目前在做的改进项 索引块 索引块由两大部分组成,其写入的方式是从左到右写入,也就是从文件头向文件尾写入.但读出的方式是先读出TsFileMetaData 再读出 TsDeviceMetaDataList 中的具体一部分.我们按照读

Mybatis 动态切换数据库

mybatis介绍: 每一个Mybatis的应用程序都以一个SqlSessionFactory对象的实例为核心.SqlSessionFactory对象实例可以通过SqlSessionFactoryBuilder对象获得.SqlSessionFactoryBuilder对象可以从XML配置文件或从Configuration类的习惯准备的实例中构建SqlSessionFactory对象. 从XML文件中构建SqlSessionFactory的实例非常简单.这里建议使用类的路径的资源文件来配置,这样我

消息中间件 RocketMQ源码解析:事务消息

关注微信公众号:[芋艿的后端小屋]有福利: RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表 RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址 您对于源码的疑问每条留言都将得到认真回复.甚至不知道如何读源码也可以请教噢. 新的源码解析文章实时收到通知.每周更新一篇左右. 1. 概述 2. 事务消息发送 2.1 Producer 发送事务消息 2.2 Broker 处理结束事务请求 2.3 Broker 生成

Android 开源项目源码解析(第二期)

Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations 源码解析 SlidingMenu 源码解析 Cling 源码解析 BaseAdapterHelper 源码分析 Side Menu.Android 源码解析 DiscreteSeekBar 源码解析 CalendarListView 源码解析 PagerSlidingTabStrip 源码解析 公共

Android xUtils3源码解析之注解模块

xUtils3源码解析系列 一. Android xUtils3源码解析之网络模块 二. Android xUtils3源码解析之图片模块 三. Android xUtils3源码解析之注解模块 四. Android xUtils3源码解析之数据库模块 初始化 public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { su