动态数据源配置

一、动态数据源上下文

package tv.zhongchi.base.common.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName DBContextHolder
 * @Author ZhangRF
 * @CreateDate 2020/6/17
 * @Decription 动态数据源上下文
 */
public class DBContextHolder {
    private final static Logger log = LoggerFactory.getLogger(DBContextHolder.class);
    // 对当前线程的操作-线程安全的
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    // 调用此方法,切换数据源
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    // 获取数据源
    public static String getDataSource() {
        return contextHolder.get();
    }

    // 删除数据源
    public static void clearDataSource() {
        contextHolder.remove();
    }

}

二、动态数据源设置DruidDBConfig 

package tv.zhongchi.base.common.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.MybatisMapWrapperFactory;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import tv.zhongchi.base.common.handler.MyMetaObjectHandler;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName DruidDBConfig
 * @Author ZhangRF
 * @CreateDate 2020/6/17
 * @Decription DruidDBConfig类被@Configuration标注,用作配置信息; DataSource对象被@Bean声明,为Spring容器所管理,表示这里定义的DataSource将覆盖其他来源的DataSource。
 */
@Configuration
@EnableTransactionManagement
public class DruidDBConfig {
    private final Logger log = LoggerFactory.getLogger(getClass());

    // adi数据库连接信息
    @Value("${spring.datasource.url}")
    private String dbUrl;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.driverClassName}")
    private String driverClassName;
    // 连接池连接信息
    @Value("${spring.datasource.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.maxActive}")
    private int maxActive;
    @Value("${spring.datasource.maxWait}")
    private int maxWait;
    @Value("${spring.profiles.active}")
    private String profile;

    @Bean // 声明其为Bean实例
    @Primary // 在同样的DataSource中,首先使用被标注的DataSource
    @Qualifier("adiDataSource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource datasource = new DruidDataSource();
        // 基础连接信息
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        // 连接池连接信息
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
        datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
        datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
        datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
        datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        String validationQuery = "select 1 from dual";
        datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
        datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
        datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
        datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
        datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
        datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
        datasource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志
        return datasource;
    }

    @Bean(name = "dynamicDataSource")
    @Qualifier("dynamicDataSource")
    public DynamicDataSource dynamicDataSource() throws SQLException {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //配置缺省的数据源
        dynamicDataSource.setDefaultTargetDataSource(dataSource());
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>(1);
        targetDataSources.put("adiDataSource", dataSource());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }


    /**
     * 注入驼峰命名
     *
     * @return
     */
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return i -> i.setObjectWrapperFactory(new MybatisMapWrapperFactory());
    }

    @Bean
    public EasySqlInjector easySqlInjector() {
        return new EasySqlInjector();
    }

    /**
     * 动态据源事务
     *
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        try {
            return new DataSourceTransactionManager(dynamicDataSource());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    /**
     * 动态数据源切换配置
     *
     * @return
     * @throws Exception
     */
    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dynamicDataSource());
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/**/*.xml"));
        sqlSessionFactory.setTypeAliasesPackage("tv.zhongchi.*.*.po");

        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        if ("test".equals(profile) || "dev".equals(profile)) {
            configuration.setLogImpl(StdOutImpl.class);
        }
        sqlSessionFactory.setConfiguration(configuration);

        //全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
        dbConfig.setIdType(IdType.AUTO);
        dbConfig.setTableUnderline(true);
        dbConfig.setCapitalMode(false);
        dbConfig.setUpdateStrategy(FieldStrategy.NOT_EMPTY);
        globalConfig.setDbConfig(dbConfig);
        globalConfig.setSqlInjector(easySqlInjector());

        globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
        sqlSessionFactory.setGlobalConfig(globalConfig);

        //分页插件
        List<Interceptor> interceptors = new ArrayList<>();
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置数据库
        paginationInterceptor.setDialectType(DbType.MYSQL.getDb());
        interceptors.add(paginationInterceptor);
        sqlSessionFactory.setPlugins(interceptors.toArray(new Interceptor[0]));
        return sqlSessionFactory.getObject();
    }

}

三、 动态数据源

package tv.zhongchi.base.common.datasource;


import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.StringUtils;
import tv.zhongchi.common.exception.BizException;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @ClassName DynamicDataSource
 * @Author ZhangRF
 * @CreateDate 2020/6/17
 * @Decription 动态数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private static Map<Object, Object> dynamicTargetDataSources = new ConcurrentHashMap<>();
    private static Map<Object, DynamicDataSourceTimer> timerMap = new ConcurrentHashMap<>();

    /**
     * 通过定时任务周期性清除不使用的数据源
     */
    @Scheduled(initialDelay = 10 * 60 * 1000, fixedRate = 10 * 60 * 1000)
    public void clearTask() {
        // 遍历timetMap,判断
        clearIdleDDS();
    }

    /**
     * 设置当前数据源
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        String datasource = DBContextHolder.getDataSource();
        if (datasource != null) {
            updateTimer(datasource);
        } else {
            datasource = "adiDataSource";
        }

        return datasource;
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {

        super.setTargetDataSources(targetDataSources);
        this.dynamicTargetDataSources = targetDataSources;
        targetDataSources.forEach((k, v) -> {
            DruidDataSource dataSource = (DruidDataSource) v;
            timerMap.put(k, new DynamicDataSourceTimer(dataSource));
        });

    }

    /**
     * 动态增加数据源
     *
     * @param dataSourceVO
     * @return
     */
    public synchronized boolean addDataSource(DataSourceVO dataSourceVO) throws Exception {
        try {
            Connection connection = null;
            try { // 排除连接不上的错误
                Class.forName(dataSourceVO.getDatasourceDriver());
                // 相当于连接数据库
                connection = DriverManager.getConnection(dataSourceVO.getDatasourceUrl(), dataSourceVO.getDatasourceUsername(), dataSourceVO.getDatasourcePassword());

            } catch (Exception e) {
                throw new BizException(401L, "数据源异常,请联系管理员");
            } finally {
                if (connection != null && !connection.isClosed()) {
                    connection.close();
                }
            }
            Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
            if (dynamicTargetDataSources2.containsKey(String.valueOf(dataSourceVO.getTenantId()))) {
                return true;
            }
            //添加数据源
            Long tenantId = dataSourceVO.getTenantId();
            if (StringUtils.isEmpty(tenantId)) {
                throw new BizException(401L, "数据源异常,请联系管理员");
            }
            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setName(String.valueOf(dataSourceVO.getTenantId()));
            druidDataSource.setDriverClassName(dataSourceVO.getDatasourceDriver());
            druidDataSource.setUrl(dataSourceVO.getDatasourceUrl());
            druidDataSource.setUsername(dataSourceVO.getDatasourceUsername());
            druidDataSource.setPassword(dataSourceVO.getDatasourcePassword());
            druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
            druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
            druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
            druidDataSource.setTimeBetweenEvictionRunsMillis(4000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            druidDataSource.setMinEvictableIdleTimeMillis(2000); //配置一个连接在池中最小生存的时间,单位是毫秒,
            druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
            druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
            druidDataSource.setRemoveAbandonedTimeout(600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为10分钟
            druidDataSource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志
            druidDataSource.init();
            this.dynamicTargetDataSources.put(tenantId, druidDataSource);
            this.setTargetDataSources(this.dynamicTargetDataSources);// 将map赋值给父类的TargetDataSources
            super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
            log.info(tenantId + "数据源初始化成功");
            return true;
        } catch (Exception e) {
            log.error(e + "");
            throw new BizException(401L, "数据源异常,请联系管理员");
        }
    }

    // 删除数据源
    public boolean delDatasources(String datasourceid) {
        Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        if (dynamicTargetDataSources2.containsKey(datasourceid)) {
            Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
            for (DruidDataSource l : druidDataSourceInstances) {
                if (datasourceid.equals(l.getName())) {
                    dynamicTargetDataSources2.remove(datasourceid);
                    DruidDataSourceStatManager.removeDataSource(l);
                    setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
                    super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }

    // 测试数据源连接是否有效
    public boolean testDatasource(DataSourceVO dataSourceVO) throws SQLException {
        Connection connection = null;
        try { // 排除连接不上的错误
            Class.forName(dataSourceVO.getDatasourceDriver());
            // 相当于连接数据库
            connection = DriverManager.getConnection(dataSourceVO.getDatasourceUrl(), dataSourceVO.getDatasourceUsername(), dataSourceVO.getDatasourcePassword());
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
        }
    }

    /**
     * 创建数据源
     *
     * @param dataSourceVO
     * @throws Exception
     */
    public void createDataSourceWithCheck(DataSourceVO dataSourceVO) throws Exception {
        String datasourceId = String.valueOf(dataSourceVO.getTenantId());
        DBContextHolder.setDataSource(datasourceId);
        Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        if (!dynamicTargetDataSources2.containsKey(datasourceId)) {
            //测试连接
            if (!testDatasource(dataSourceVO)) {
                this.addDataSource(dataSourceVO);
            }
        }

    }

    private void clearIdleDDS() {
        timerMap.forEach((k, v) -> {
            if (v.checkAndClose()) {
                delDatasources(k.toString());
            }
        });
    }

    private void updateTimer(String lookupKey) {
        // 更新时间戳
        DynamicDataSourceTimer timer = timerMap.get(lookupKey);
        if (timer != null) {
            timer.refreshTime();
        }
    }

}

四、动态数据源切面拦截

package tv.zhongchi.base.common.datasource;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import tv.zhongchi.base.user.mapper.DatasourceMapper;
import tv.zhongchi.base.user.po.DatasourcePO;
import tv.zhongchi.common.constants.RedisConstants;
import tv.zhongchi.common.enums.ClientEnum;
import tv.zhongchi.common.exception.BizException;
import tv.zhongchi.common.util.HttpRequestUtil;
import tv.zhongchi.common.util.JsonUtil;
import tv.zhongchi.common.util.JwtUtil;
import tv.zhongchi.common.util.StringUtil;
import tv.zhongchi.common.vo.ResultVO;
import tv.zhongchi.common.vo.query.EConditionOperator;
import tv.zhongchi.common.vo.query.QueryConditions;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName DynamicDataSource
 * @Author ZhangRF
 * @CreateDate 2020/6/17
 * @Decription 动态数据源切面拦截
 */
@Aspect
@Component
@Order(1)
@Slf4j
@RestControllerAdvice
public class DynamicDataSourceAspect {
    @Resource
    private DynamicDataSource dynamicDataSource;
    @Resource
    private DatasourceMapper datasourceMapper;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final String TENANTID = "tenantId";

    @Around("execution(* tv.zhongchi.*.*..controller..*(..)) || execution(* tv.zhongchi.*.*.*(..))")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        Object result = null;
        ResultVO resultVO = ResultVO.fail();
        String key = RedisConstants.DATA_SOURCE_INFO_KEY_PREFIX;
        try {
            HttpServletRequest request = sra.getRequest();
            HttpSession session = sra.getRequest().getSession(true);
            String tenantId = (String) session.getAttribute("tenantId");
            if (StringUtil.isEmpty(tenantId)) {
                tenantId = request.getParameter("tenantId");
            }

            if (!StringUtil.isEmpty(tenantId) && !tenantId.equals(String.valueOf(0))) {
                //创建数据源
                key = key + tenantId;
                DataSourceVO dataSourceVO = (DataSourceVO) redisTemplate.opsForValue().get(key);
                if (dataSourceVO == null || dataSourceVO.getTenantId() == null) {
                    QueryConditions<DatasourcePO> datasourcePOQueryConditions = new QueryConditions<>();
                    datasourcePOQueryConditions.addCondition("tenant_id", EConditionOperator.EQULS, tenantId);
                    DatasourcePO datasourcePO = datasourceMapper.selectOne(datasourcePOQueryConditions.getQueryWrapper());
                    if (datasourcePO == null || datasourcePO.getId() == null) {
                        resultVO.setCode(401);
                        resultVO.setMessage("数据源不存在,请重新登陆");
                        result = resultVO;
                        return result;
                    } else {
                        dataSourceVO = new DataSourceVO();
                        BeanUtils.copyProperties(datasourcePO, dataSourceVO);
                        redisTemplate.opsForValue().set(key + datasourcePO.getTenantId(), dataSourceVO, 1, TimeUnit.DAYS);
                    }
                }

                try {
                    dynamicDataSource.createDataSourceWithCheck(dataSourceVO);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                //切换数据源
                DBContextHolder.setDataSource(String.valueOf(dataSourceVO.getTenantId()));
                result = jp.proceed();
            } else {
                try {
                    //根据token查询用户租户Id
                    String token = HttpRequestUtil.getToken();
                    String loginCode = JwtUtil.getLoginCode(token);
                    ClientEnum clientEnum = JwtUtil.getClientEnum(token);
                    Object o = redisTemplate.opsForValue().get(RedisConstants.USER_LOGIN_TOKEN_KEY_PREFIX + loginCode + clientEnum);
                    String s = JsonUtil.toJSONString(o);
                    JSONObject parse = (JSONObject) JSONObject.parse(s);
                    tenantId = parse.getString("tenantId");
                } catch (Exception e) {
                }

                if (StringUtils.isEmpty(tenantId) || tenantId.equals(String.valueOf(0)) || tenantId.equals(String.valueOf(1))) {
                    //当租户Id为空时或者为0,不切换数据源
                    result = jp.proceed();
                } else {
                    //创建数据源
                    key = key + tenantId;
                    DataSourceVO dataSourceVO = (DataSourceVO) redisTemplate.opsForValue().get(key);
                    if (dataSourceVO == null || dataSourceVO.getTenantId() == null) {
                        QueryConditions<DatasourcePO> datasourcePOQueryConditions = new QueryConditions<>();
                        datasourcePOQueryConditions.addCondition("tenant_id", EConditionOperator.EQULS, tenantId);
                        DatasourcePO datasourcePO = datasourceMapper.selectOne(datasourcePOQueryConditions.getQueryWrapper());
                        if (datasourcePO == null || datasourcePO.getId() == null) {
                            resultVO.setCode(401);
                            resultVO.setMessage("数据源不存在,请重新登陆");
                            result = resultVO;
                            return result;
                        } else {
                            dataSourceVO = new DataSourceVO();
                            BeanUtils.copyProperties(datasourcePO, dataSourceVO);
                            redisTemplate.opsForValue().set(key + datasourcePO.getTenantId(), dataSourceVO, 1, TimeUnit.DAYS);
                        }
                    }

                    try {
                        dynamicDataSource.createDataSourceWithCheck(dataSourceVO);
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new BizException(401L, "数据源异常,请联系管理员");
                    }
                    //切换数据源
                    DBContextHolder.setDataSource(String.valueOf(dataSourceVO.getTenantId()));
                    result = jp.proceed();
                }
            }
        } catch (BizException bizException) {
            throw bizException;
        } catch (AuthorizationException authorizationException) {
            authorizationException.printStackTrace();
            log.error("没有权限异常", authorizationException);
            throw new BizException(402L, "没有权限,请联系管理员");
        } catch (Exception ex) {
            ex.printStackTrace();
            Throwable cause = ex.getCause();
            if (!StringUtils.isEmpty(cause) && !StringUtils.isEmpty(cause.getMessage())) {
                String message = cause.getMessage();
                if (message.equals("没有权限,请联系管理员") || message.equals("token失效,请重新登录") ||
                        message.equals("数据源异常,请联系管理员")) {
                    throw cause;
                }
            }
            log.error("sql异常", ex);
            result = ResultVO.fail("系统异常,请联系管理员!");
        }
        return result;
    }

}

五、配置动态数据源定时器

package tv.zhongchi.base.common.datasource;

import javax.sql.DataSource;

/**
 * @ClassName DynamicDataSourceTimer
 * @Author ZhangRF
 * @CreateDate 2020/7/16
 * @Decription 动态数据源定时器
 */
public class DynamicDataSourceTimer {

    /**
     * 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为5分钟。
     */
    private static long idlePeriodTime = 5 * 60 * 1000;

    /**
     * 动态数据源
     */
    private DataSource dds;

    /**
     * 上一次访问的时间
     */
    private long lastUseTime;

    public DynamicDataSourceTimer(DataSource dds) {
        this.dds = dds;
        this.lastUseTime = System.currentTimeMillis();
    }

    /**
     * 更新最近访问时间
     */
    public void refreshTime() {
        lastUseTime = System.currentTimeMillis();
    }

    /**
     * 检测数据连接是否超时关闭。
     *
     * @return true-已超时关闭; false-未超时
     */
    public boolean checkAndClose() {

        if (System.currentTimeMillis() - lastUseTime > idlePeriodTime) {
            return true;
        }

        return false;
    }

}

六、DataSourceVO 

package tv.zhongchi.base.common.datasource;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * @ClassName DatasiurcePO
 * @CreateDate 2020-06-16 09:26:26
 * @Author ZhangRF
 * @Decription 租户数据源
 */
@Data
public class DataSourceVO implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     *
     */
    private Long id;
    /**
     * 租户id
     */
    private Long tenantId;
    /**
     * 数据源url
     */
    private String datasourceUrl;
    /**
     * 数据源用户名
     */
    private String datasourceUsername;
    /**
     * 数据源密码
     */
    private String datasourcePassword;
    /**
     * 数据源驱动
     */
    private String datasourceDriver;
    /**
     * 是否启用(1是0否)
     */
    private Integer statusId;
    /**
     * 是否删除:0否,1是
     */
    private Integer isDelete;
    /**
     * 备注
     */
    private String remark;
    /**
     * 创建人
     */
    private String createUser;
    /**
     * 创建时间
     */
    private Date gmtCreate;
    /**
     * 更新人
     */
    private String modifiedUser;
    /**
     * 更新时间
     */
    private Date gmtModified;

}
原文地址:https://www.cnblogs.com/zhangrongfei/p/14807707.html