Springboot/MybatisPlus动态切换数据源

article/2025/9/30 22:43:41

1.1 简述

最近项目中有动态切换数据源需求,主要是要动态切换数据源进行数据的获取,现将项目中实现思路及代码提供出来,供大家参考。当然切换数据源还有其他的方式比如使用切面的方式,其实大体思路是一样的。

1.2 设计思路与代码示例

数据库连接池druid

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.21</version>
</dependency>

ymldruid配置:

druid:initial-size: 5                                       # 初始化大小min-idle: 10                                          # 最小连接数max-active: 20                                        # 最大连接数max-wait: 60000                                       # 获取连接时的最大等待时间

配置文件中配置默认的数据源datasource,默认数据源中需要建立一张各租户的数据源表:
在这里插入图片描述
上图表中有redis-database字段为切换redis库时使用的字段,如不需要可自行修改。

创建DataSourceContextHolder用于切换数据源,使用threadlocal保留当前线程数据源信息:

@Slf4j
public class DataSourceContextHolder {private static final ThreadLocal<String> DATA_SOURCE = new ThreadLocal<>();/*** 切换数据源*/public static void setDataSource(String datasourceId) {DATA_SOURCE.set(datasourceId);log.info("已切换到数据源:{}",datasourceId);}public static String getDataSource() {return DATA_SOURCE.get();}/*** 删除数据源*/public static void removeDataSource() {DATA_SOURCE.remove();log.info("已切换到默认主数据源");}
}

继承AbstractRoutingDataSource实现根据不同请求切换数据源:

@Slf4j
@Data
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {private boolean debug = true;/*** 存储注册的数据源*/private volatile Map<Object, Object> custom;@Overrideprotected Object determineCurrentLookupKey() {String datasourceId = DataSourceContextHolder.getDataSource();if(!StringUtils.isEmpty(datasourceId)){Map<Object, Object> map = this.custom;if(map.containsKey(datasourceId)){log.info("当前数据源是:{}",datasourceId);}else{log.info("不存在数据源:{}",datasourceId);return null;}}else{log.info("当前是默认数据源");}return datasourceId;}@Overridepublic void setTargetDataSources(Map<Object, Object> param) {super.setTargetDataSources(param);this.custom = param;}/*** @Description: 检查数据源是否已经创建* @param dataSource*/public void checkCreateDataSource(DatabaseList dataSource){String datasourceId = dataSource.getFactoryCode();Map<Object, Object> map = this.custom;if(map.containsKey(datasourceId)){//这里检查一下之前创建的数据源,现在是否连接正常DruidDataSource druidDataSource = (DruidDataSource) map.get(datasourceId);boolean flag = true;DruidPooledConnection connection = null;try {connection = druidDataSource.getConnection();} catch (SQLException throwAbles) {//抛异常了说明连接失效吗,则删除现有连接log.error(throwAbles.getMessage());flag = false;delDataSources(datasourceId);//}finally {//如果连接正常记得关闭if(null != connection){try {connection.close();} catch (SQLException e) {log.error(e.getMessage());}}}if(!flag){createDataSource(dataSource);}}else {createDataSource(dataSource);}}/*** @Description: 创建数据源* @param dataSource*/private void createDataSource(DatabaseList dataSource) {try {Class.forName("com.mysql.cj.jdbc.Driver");Connection connection = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());if(connection==null){log.error("数据源配置有错误,DataSource:{}",dataSource);}else{connection.close();}DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setName(dataSource.getFactoryCode());druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");druidDataSource.setUrl(dataSource.getUrl());druidDataSource.setUsername(dataSource.getUser());druidDataSource.setPassword(dataSource.getPassword());druidDataSource.setMaxActive(20);druidDataSource.setMinIdle(5);//获取连接最大等待时间,单位毫秒druidDataSource.setMaxWait(6000);String validationQuery = "select 1 from dual";//申请连接时执行validationQuery检测连接是否有效,防止取到的连接不可用druidDataSource.setTestOnBorrow(true);druidDataSource.setValidationQuery(validationQuery);druidDataSource.init();this.custom.put(dataSource.getFactoryCode(),druidDataSource);// 将map赋值给父类的TargetDataSourcessetTargetDataSources(this.custom);// 将TargetDataSources中的连接信息放入resolvedDataSources管理super.afterPropertiesSet();} catch (Exception e) {log.error("数据源创建失败",e);}}/*** @Description: 删除数据源* @param datasourceId*/private void delDataSources(String datasourceId) {Map<Object, Object> map = this.custom;Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();for (DruidDataSource dataSource : druidDataSourceInstances) {if (datasourceId.equals(dataSource.getName())) {map.remove(datasourceId);//从实例中移除当前dataSourceDruidDataSourceStatManager.removeDataSource(dataSource);// 将map赋值给父类的TargetDataSourcessetTargetDataSources(map);// 将TargetDataSources中的连接信息放入resolvedDataSources管理super.afterPropertiesSet();}}}
}

接下来配置默认数据源,还有数据库连接池的信息,以及将数据源配置到sql工厂当中:

@Configuration
@Slf4j
public class DruidDBConfig {@Value("${spring.datasource.url}")private String dbUrl;@Value("${spring.datasource.username}")private String username;@Value("${spring.datasource.password}")private String password;@Value("${spring.datasource.driver-class-name}")private String driverClassName;@Value("${spring.datasource.druid.initial-size}")private int initialSize;@Value("${spring.datasource.druid.min-idle}")private int minIdle;@Value("${spring.datasource.druid.max-active}")private int maxActive;@Value("${spring.datasource.druid.max-wait}")private int maxWait;@Bean@Primary@Qualifier("mainDataSource")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);//是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。datasource.setPoolPreparedStatements(false);datasource.setMaxPoolPreparedStatementPerConnectionSize(20);//申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用datasource.setTestOnBorrow(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。datasource.setTestWhileIdle(true);//用来检测连接是否有效的sqldatasource.setValidationQuery("select 1 from dual");//配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒datasource.setTimeBetweenEvictionRunsMillis(60000);//配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000datasource.setMinEvictableIdleTimeMillis(180000);datasource.setKeepAlive(true);return datasource;}@Bean(name = "dynamicDataSource")@Qualifier("dynamicDataSource")public DynamicRoutingDataSource dynamicDataSource() throws SQLException {DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();dynamicDataSource.setDebug(false);//配置缺省的数据源dynamicDataSource.setDefaultTargetDataSource(dataSource());Map<Object, Object> targetDataSources = new HashMap<Object, Object>();//额外数据源配置 TargetDataSourcestargetDataSources.put("mainDataSource", dataSource());dynamicDataSource.setTargetDataSources(targetDataSources);return dynamicDataSource;}@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dynamicDataSource());//对新的SqlSessionFactory配置 修改mybatis-plus Page自动分页失效问题 以及 找不到xml问题MybatisConfiguration configuration = new MybatisConfiguration();configuration.addInterceptor(new MybatisPlusConfig().paginationInterceptor());sqlSessionFactoryBean.setConfiguration(configuration);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*.xml"));return sqlSessionFactoryBean.getObject();}/*** @Description: 将动态数据加载类添加到事务管理器* @param dataSource* @return org.springframework.jdbc.datasource.DataSourceTransactionManager*/@Beanpublic DataSourceTransactionManager transactionManager(DynamicRoutingDataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}

现在就可以根据数据源表中唯一值切换数据源了,调用changeDB(数据源唯一值):

@ResourceDatabaseListService databaseListService;@Resourceprivate DynamicRoutingDataSource dynamicRoutingDataSource;@Overridepublic boolean changeDB(String datasourceId) {//切到默认数据源DataSourceContextHolder.removeDataSource();//找到所有的配置List<DatabaseList> databaseListList = databaseListService.list();if(!CollectionUtils.isEmpty(databaseListList)){for (DatabaseList d : databaseListList) {if(d.getFactoryCode().equals(datasourceId)){//判断连接是否存在,不存在就创建dynamicRoutingDataSource.checkCreateDataSource(d);//切换数据源DataSourceContextHolder.setDataSource(d.getFactoryCode());return true;}}}return false;}

以上就完成了所有步骤,可以切换数据源了。

在我的项目里,我在登录的拦截器中校验完权限后判断属于哪个数据源来切换。

下面说一下redis库的动态切换:

同样使用threadlocal保留当前线程的redis库,在getset时设置redis数据库索引:

@Slf4j
@Component
public class RedisUtil {private static RedisTemplate redisTemplate;@Autowiredpublic void setRedisTemplate(RedisTemplate redisTemplate) {RedisUtil.redisTemplate = redisTemplate;}private static ThreadLocal<Integer>   database          = new ThreadLocal<Integer>();public static Integer getDatabase() {return (Integer) database.get();}public static void setDatabase(Integer db) {database.set(db);}/*** 设置数据库索引**/public static void setDbIndex() {//默认使用1Integer dbIndex = 1;if(database.get()!=null){dbIndex = database.get();}LettuceConnectionFactory redisConnectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();if (redisConnectionFactory == null) {return;}redisConnectionFactory.setDatabase(dbIndex);redisTemplate.setConnectionFactory(redisConnectionFactory);// 属性设置后redisConnectionFactory.afterPropertiesSet();// 重置连接redisConnectionFactory.resetConnection();}/*** 普通缓存获取** @param key 键* @return 值*/public static Object get(String key) {setDbIndex();return key == null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入** @param key   键* @param value 值* @return true成功 false失败*/public static boolean set(String key, Object value) {setDbIndex();try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}

1.3 原理解析

原理参考:https://blog.csdn.net/zhang_java_11/article/details/121626842?spm=1001.2014.3001.5506
在这里插入图片描述
其实我们新建数据库连接的时候也是通过DataSource来获取连接的,这里的AbstractRoutingDataSource也是通过了DataSource中的getConnection方法来获取连接的。
在这里插入图片描述
这个类里维护了两个Map来存储数据库连接信息:

@Nullable
private Map<Object, Object> targetDataSources; @Nullable
private Object defaultTargetDataSource;private boolean lenientFallback = true;private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();@Nullable
private Map<Object, DataSource> resolvedDataSources;@Nullable
private DataSource resolvedDefaultDataSource;

下面对上面的几个属性进行说明:

  • 其中第一个targetDataSources是一个Map对象,在我们上面第五步创建DynamicDataSource实例的时候将多个数据源的DataSource类,放入到这个Map中去,这里的Key是枚举类,values就是DataSource类。
    在这里插入图片描述
  • 第二个defaultTargetDataSource是默认的数据源,就是DynamicDataSource中唯一重写的方法来给这个对象赋值的。
    在这里插入图片描述
  • 第三个lenientFallback是一个标识,是当指定数据源不存在的时候是否采用默认数据源,默认是true,设置为false之后如果找不到指定数据源将会返回null.
    在这里插入图片描述
  • 第四个dataSourceLookup是用来解析指定的数据源对象为DataSource实例的。默认是JndiDataSourceLookup实例,继承自DataSourceLookup接口。
  • 第五个resolvedDataSources也是一个Map对象,这里是存放指定数据源解析后的DataSource对象。
    在这里插入图片描述
    第六个resolvedDefaultDataSource是默认的解析后的DataSource数据源对象上面的getConnection方法就是从这个变量中拿到DataSource实例并获取连接的。
    在这里插入图片描述

1.4 总结

多数据源切换的需求在我们日常开发中可能会经常碰到,主要是通过几个主要的类进行实现的,实现过程并不复杂,在此记录一下,希望能够帮助到有需要的童鞋。


http://chatgpt.dhexx.cn/article/iWBD9tSl.shtml

相关文章

Spring数据源配置

目录 Spring相关的API ApplicationContext的继承体系 ApplicationContext的实现类 getBean()方法使用 Spring配置数据源(连接池) 数据源的开发步骤 加载配置文件方式创建 将DateSource的创建权交给Spring容器去完成 抽取jdbc配置文件 Spring相关的API ApplicationContex…

Java 动态数据源配置

目录 一、动态数据源介绍 1. AbstractRoutingDataSource 2. 实现逻辑 二、源代码 1. 修改配置文件类 2. 创建数据源枚举 3. 数据源切换处理 4. 继承AbstractRoutingDataSource 5. 注入数据源 6. 自定义多数据源切换注解 7. AOP拦截类的实现 8. 使用切换数据源注解 …

SpringBoot实现多数据源配置

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; SpringBoot实现多数据源配置 ⏱️ 创作时间&#xff1a; 2022年06月13…

SAP 数据源

SAP BW或者BI作为数据仓库会抽取SAP系统数据和非SAP系统数据&#xff0c;源系统的主要类型有以下几种&#xff1a; 1、SAP系统&#xff1a;R/3、S/4、CRM、等&#xff0c; 2、文本文件&#xff1a;将数据库转化为XML或者CSV文件放在FTP上&#xff0c;然后去读取数据&#xf…

Python使用tsne进行高维数据可视化实战:二维可视化、三维可视化

Python使用tsne进行高维数据可视化实战:二维可视化、三维可视化 # 绘制二维可视化图像并添加标签字符函数 def plot_embedding(data, label, title):x_min, x_max = np.min(data, 0), np.max(data, 0)data = (data - x_min) / (x_max - x_min)fig = plt.figure()ax = plt.s…

降维算法PCA的应用----高维数据的可视化

文章目录 序言废话不多说看代码导入相关模块数据提取降维降维后数据信息展示新的特征矩阵 可视化关于X_dim2[yi, 0]的解释 总结 序言 当我们拿到一堆数据的时候&#xff0c;几乎不可能通过我们的肉眼分辨出数据的分布情况&#xff0c;这时候就想要通过图展示数据的分布&#x…

学习笔记 | 用距离之距离(DoD)变换改进高维数据可视化

文章目录 一、论文关键信息二、主要内容三、总结CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一、论文关键信息 论文标题:Improved visualization of high-dimensional data using the distance-of-distance transformation 论文地址:https://doi.org/10.1371/journal.…

数据可视化的历史

数据可视化的历史 可视化发展史与测量、绘画、人类现代文明的启蒙和科技的发展一脉相承。在地图、科学与工程制图、统计图表中&#xff0c;可视化理念与技术已经应用和发展了数百年。 17世纪之前&#xff1a;图表萌芽 16世纪时&#xff0c;人类已经掌握了精确的观测技术和设…

【数据可视化】复杂高维多元数据的可视化

1 高维多元数据 每个数据对象有两个或两个以上独立或者相关属性的数据。高维指数据具有多个独立属性&#xff0c;多元指数据具有多个相关属性。由于研究者在很多情况下不确定数据的属性是否独立&#xff0c;因此通常简单地称之为多元数据。例如&#xff1a;电脑配置。 高维多元…

平行坐标图:高维数据可视化必备图形

关于数据可视化&#xff0c;我们之前分享过很多基础图表和进阶图表&#xff0c;都是一些我们常见的图表&#xff0c;如折线图&#xff0c;柱状图&#xff0c;饼图等等。今天分享一个大家应该见过但是不那么熟悉的图表-平行坐标图。 平行坐标图的定义 平行坐标图可以说是折线图…

Umap高维数据可视化与降维

Umap解决高维数据可视化的问题&#xff0c;以及高效降维。 Umap地址:https://github.com/lmcinnes/umap 文档地址&#xff1a;UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction — umap 0.5 documentation 1.pip通过清华镜像安装方式&#xff…

【数据艺术科技1】基于pyhon的高维数据可视化。(1、2维)

引言 描述性分析是与数据科学项目甚至特定研究相关的任何分析生命周期的核心组成部分之一。数据聚合、汇总和可视化是支持这一数据分析领域的一些主要支柱。从传统商业智能时代到如今的人工智能时代&#xff0c;数据可视化一直是一种强大的工具&#xff0c;并因其在提取正确信…

python高维数据可视化_【机器学习】(十六)主成分分析PCA:高维数据可视化、特征提取...

主成分分析(PCA)是一种旋转数据集的方法,旋转后的特征在统计上不相关。 用PCA做数据变换 首先,算法在原始数据点集中,找到方差最大的方向(包含最多信息),标记为‘成分1’。->找到与“成分1”正交(成直角)且包含最多信息的方向,标记为“成分2”。利用这一过程找到的方向…

TSNE 高维数据可视化

TSNE 高维数据可视化 标签&#xff1a; python 机器学习 神经网络 在神经网络中&#xff0c;我们最后一层一般都是高纬度的数据&#xff0c;但是有时候我们可能想看一下这些高纬度数据的分布情况&#xff0c;这个时候就需要用TSNE&#xff0c;其实TSNE本质上就是先利用PCA降维…

PCA实现高维数据可视化

1 简介 PCA&#xff08;Principal Component Analysis&#xff09;即主成分分析是最常见的降维方法&#xff0c; 它是一种统计方法。用于高维数据集的探索与可视化&#xff0c;还可用于数据的压缩和预处理。可通过正交变换把具有相关性的高维变量转换为线性无关的低维变量&…

高维数据可视化之t-SNE算法

https://blog.csdn.net/hustqb/article/details/78144384 t-sne数学原理https://zhuanlan.zhihu.com/p/57937096 什么是t-SNE&#xff1f; t-SNE的主要用途是可视化和探索高维数据。 它由Laurens van der Maatens和Geoffrey Hinton在JMLR第九卷(2008年)中开发并出版。 t-SNE…

Python 数据可视化学习笔记 之高维数据可视化及其方法

一、高维数据 高维数据泛指高维&#xff08;multidimensional&#xff09; 和多变量&#xff08;multivariate&#xff09;数据 -- 高维是指数据具有多个独立属性 -- 多变量是指数据具有多个相关属性 高维数据可视化的挑战&#xff1a; 如何呈现单个数据点的各属性的数据值分…

高维数据可视化示例

高维数据可视化示例 文中代码均在Jupyter Notebook中运行 文中所需两个数据文件在下面给出。 文中数据集下载1 文中数据集下载2 目录 高维数据可视化示例单变量分析多变量分析可视化二维数据可视化三维数据可视化四维数据可视化 5 维数据可视化 6 维数据&#xff08;6-D&#…

python科学计算库安装

python科学计算相关的库包括numpy&#xff0c;scipy&#xff0c;matplotlib等&#xff0c;但是自己安装比较不容易&#xff0c;倒不是安装过程有多难&#xff0c;而是会出现各种各样的问题&#xff0c;现在做一记录 安装顺序numpy -> scipy - > matplotlib&#xff0c; …

python怎么进行计算_python怎么计算

广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 下面是python中的一个函数计算代码:loops=25000000 from math import*a=range(1,loops)def f(x):return 3*cos(x)+4*sin(x)**2%timeit r=(f(x) for x in a)效率…