springBoot-Mybatis-Plus 多数据源切换实现

news2024/12/26 0:55:23

前言:本文主要通过AbstractRoutingDataSource,实现根据 http 访问携带的标识动态切换数据源;

1 AbstractRoutingDataSource 介绍:

AbstractRoutingDataSource 是 Spring 框架中的一个抽象类,它可以用来实现动态数据源切换。在多数据源场景下,AbstractRoutingDataSource 可以根据不同的请求来动态地选择合适的数据源进行操作,以达到高效利用多个数据源的目的。

AbstractRoutingDataSource 并不是直接连接数据库的数据源,它只是一个路由数据源,它负责根据一定的规则选择一个真正的数据源来执行数据操作。它的作用可以归纳为以下几点:

(1). 实现多数据源动态切换:AbstractRoutingDataSource 可以通过动态的选定数据源,达到多数据源操作的目的。特别在分布式环境中,可以根据业务需求将数据进行分片,然后将不同的分片数据存储在不同的数据库中,这样就能实现数据负载均衡和高可用性。

(2)… 封装数据库连接池和连接的获取逻辑:AbstractRoutingDataSource 通过封装多个数据源连接池的实现细节,屏蔽底层数据源的细节,使得业务代码不需要关心连接的获取和释放,从而简化了业务代码的编写。

(3). 实现数据源的动态切换:AbstractRoutingDataSource 可以通过动态切换数据源,实现数据源的动态切换,从而在不影响系统正常运行的情况下,能够对数据源进行升级、迁移等操作。

综上所述,AbstractRoutingDataSource 的主要作用是实现多数据源的动态切换,这在多个数据库之间实现负载均衡、数据迁移、分片存储等场景下,可以大大提高系统的性能和可用性。

2 springBoot 集成:

2.1 pom.xml 引入jar:

 <dependency>
    <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.5.2</version>
 </dependency>
 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.21</version>
 </dependency>

2.2 数据源解析类:
DataSourceConfig:


import com.example.dynamicdemo.config.DynamicDataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.sql.DataSource;
import java.util.*;

/**
 * @author du_imba
 */
@Configuration
public class DataSourceConfig {

    private static final Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);

    @Autowired
    private Environment environment;
    private static final String SEP = ",";

    @Bean
    public DataSource getDynamicDataSource() {
        DynamicDataSource routingDataSource = new DynamicDataSource();
        List<String> dataSourceKeys = new ArrayList<>();

        Iterable sources = ConfigurationPropertySources.get(environment);
        Binder binder = new Binder(sources);



        BindResult bindResult = binder.bind("datasource.tinyid", Properties.class);
        Properties properties= (Properties) bindResult.get();
        String names = properties.getProperty("names");
        String dataSourceType = properties.getProperty("type");


//        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "datasource.tinyid.");
//        String names = propertyResolver.getProperty("names");
//        String dataSourceType = propertyResolver.getProperty("type");

        Map<Object, Object> targetDataSources = new HashMap<>(4);
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDataSourceKeys(dataSourceKeys);
        // 多个数据源
        for (String name : names.split(SEP)) {

            Map<String, Object> dsMap = getSubProperties(name + ".",properties);
            DataSource dataSource = buildDataSource(dataSourceType, dsMap);
            buildDataSourceProperties(dataSource, dsMap);
            targetDataSources.put(name, dataSource);
            dataSourceKeys.add(name);
        }
        // 设置默认数据源
        routingDataSource.setDefaultTargetDataSource(targetDataSources.get("primary"));
        return routingDataSource;
    }

    private Map<String, Object> getSubProperties(String s,Properties properties) {
        Map<String, Object> dsMap = new HashMap<>(1<<2);

        dsMap.put("driver-class-name",properties.get(s+"driver-class-name"));
        dsMap.put("url",properties.get(s+"url"));
        dsMap.put("username",properties.get(s+"username"));
        dsMap.put("password",properties.get(s+"password"));
        return dsMap;
    }

    private void buildDataSourceProperties(DataSource dataSource, Map<String, Object> dsMap) {
        try {
            // 此方法性能差,慎用
            BeanUtils.copyProperties(dataSource, dsMap);
        } catch (Exception e) {
            logger.error("error copy properties", e);
        }
    }

    private HikariDataSource buildDataSource(String dataSourceType, Map<String, Object> dsMap) {
        try {
//            String className = DEFAULT_DATASOURCE_TYPE;
//            if (dataSourceType != null && !"".equals(dataSourceType.trim())) {
//                className = dataSourceType;
//            }
//            Class<? extends DataSource> type = (Class<? extends DataSource>) Class.forName(className);
            String driverClassName = dsMap.get("driver-class-name").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();

            return DataSourceBuilder.create()
                    .driverClassName(driverClassName)
                    .url(url)
                    .username(username)
                    .password(password)
//                    .type(type)
                    .type(HikariDataSource.class)
                    .build();

        } catch (Exception e) {
            logger.error("buildDataSource error", e);
            throw new IllegalStateException(e);
        }
    }


}

DynamicDataSource 路由db:


import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.List;
import java.util.Random;

/**
 * @author du_imba
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private  static  final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    private List<String> dataSourceKeys;

    @Override
    protected Object determineCurrentLookupKey() {
//        if(dataSourceKeys.size() == 1) {
//            return dataSourceKeys.get(0);
//        }
//        Random r = new Random();
//        return dataSourceKeys.get(r.nextInt(dataSourceKeys.size()));
        return getDb();
    }

    public List<String> getDataSourceKeys() {
        return dataSourceKeys;
    }

    public void setDataSourceKeys(List<String> dataSourceKeys) {
        this.dataSourceKeys = dataSourceKeys;
    }

    public static void setDb(String db){
        threadLocal.set(db);
    }
    public static String getDb(){
        return threadLocal.get();
    }
    public static void clear() {
        threadLocal.remove();
    }
}

2.3 拦截http 请求,设置本次访问的db:
HttpRequestDynamic:




import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class HttpRequestDynamic implements HandlerInterceptor {
    final static ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            // 不是 httpreuqest 请求直接放行
            return true;
        }
        DynamicDataSource.setDb(request.getHeader("db"));
        threadLocal.set(true);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在方法执行完毕或者执行报错后,移除数据源
        if (null != threadLocal.get() && threadLocal.get()) {
            DynamicDataSource.clear();
        }
        threadLocal.remove();
    }
}

WebConfiguration 拦截:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@Import({HttpRequestDynamic.class})
public class WebConfiguration implements WebMvcConfigurer {
    @Autowired
    private HttpRequestDynamic httpRequestDynamic;

    /**
     * 拦截器配置
     *
     * @param registry 注册类
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(httpRequestDynamic).addPathPatterns("/**")
                .excludePathPatterns(
                        "/file/get/*",
                        "/image/view/*",
                        "/**/error"
                );
    }
}

2.4 application.properties

server.port=9999
server.context-path=/tinyid

batch.size.max=100000

#datasource.tinyid.names=primary
datasource.tinyid.names=primary,secondary

datasource.tinyid.primary.driver-class-name=com.mysql.cj.jdbc.Driver
datasource.tinyid.primary.url=jdbc:mysql://localhost:3406/d1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
datasource.tinyid.primary.username=root
datasource.tinyid.primary.password=ddsoft
#datasource.tinyid.primary.maxActive=10

datasource.tinyid.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
datasource.tinyid.secondary.url=jdbc:mysql://localhost:3406/d2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
datasource.tinyid.secondary.username=root
datasource.tinyid.secondary.password=ddsoft
#datasource.tinyid.secondary.testOnBorrow=false
#datasource.tinyid.secondary.maxActive=10



2.5 请求:header 头放入本次的db
在这里插入图片描述

3 总结:

本文主要通过拦截http 请求,解析本次要访问的db,然后将db 设置到DynamicDataSource的ThreadLocal 中,使得访问数据库时获取到对应的db连接完成操作;

git 地址参考:https://codeup.aliyun.com/61cd21816112fe9819da8d9c/dynamic-demo.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/544860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年数据分析的就业薪资水平情况

2023年数据分析的就业薪资水平情况 数据已经成为我们工作生活不可缺少的一部分&#xff0c;也成为企业提高竞争力的有效支撑。随着越来越的企业进行数字化转型&#xff0c;对于数据的需求也将越来越大&#xff0c;那么对于正在学习数据分析或者想学习数据分析的小伙伴来说&…

浅析基于AI视频智能识别技术的医疗废弃物智能监管及风险预警方案

一、方案背景 医疗废弃物含有大量的细菌、病毒、化学污染物等&#xff0c;若是回收处置不当、工作人员防护不到位等&#xff0c;会严重影响公众及个人的健康及周围环境。 对医疗废弃物的规范管理&#xff0c;也成为医疗废弃物处置行业的重要一环。传统视频监控方案主要依靠监…

漏电保护插座插排真的有用吗?同为科技(TOWE)漏保系列PDU产品

所谓漏电保护&#xff0c;是指当电气设备绝缘发生故障&#xff0c;电线和地之间、线路和线路之间、工作回路与不能带电的金属壳体形成电流通路&#xff0c;叫做漏电&#xff1b;为预防漏电对人体造成伤害&#xff0c;就产生了各种漏电保护装置&#xff0c;当电路中的漏电流超过…

数字孪生:双碳目标推动下的汽车动力电池发展

据中汽协统计&#xff0c;2022年我国新能源汽车持续爆发式增长&#xff0c;销量超680万辆&#xff0c;已连续8年位居世界第一&#xff0c;保持“快车道”发展态势&#xff0c;引起西方发达国家的高度重视。相当一部分国家以产品全生命周期碳排放为基础&#xff0c;试图建立新的…

一文带你了解移动入库指南(详细版)

​ 移动入库认证周期&#xff1a; 常规为 4-6 周 中国移动是一家基于 GSM、TD-LTE、FDD-LTE 制式网络的移动通信运营商。日前已建成 5G 基站近 39 万个&#xff0c;并且全面推动 SA 网络&#xff0c;同时和中国广电共同发展 5G 网络。作为全球 5G 网络覆盖广、用户规模大的通信…

Q1业绩整体回暖,影视行业找到增长新路径

凛冬已过&#xff0c;影视行业恢复了生机。 数据显示&#xff0c;今年一季度&#xff0c;影视院线板块全部上市公司分别实现营收、归母净利111.86亿元、10.15亿元&#xff0c;同比增幅为1.44%和53.76%。在经济复苏的背景下&#xff0c;影视行业实现了扭亏为盈和跨越式增长。 …

Fiddler抓包丨最常用功能实战演练

目录 一. 停止抓包 二. 清空会话窗 三. 过滤请求 只显示目标请求 只抓取目标端的请求 四. 解码 五. 设置断点 伪造客户端请求 伪造服务器响应 注意事项 六. 总结 结语 通过上一篇文章Fiddler移动端抓包&#xff0c;我们知道了Fiddler抓包原理以及怎样进行移动端抓包…

虎牙直播在微服务改造的实践总结

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战、定制、远程&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面…

一文快速了解浏览器Sui Explorer

Sui作为一条基于第一原理重新设计和构建而成的L1公链&#xff0c;所有区块和交易信息皆公开透明&#xff0c;每个人都能自行查看。通过Sui链上浏览器&#xff0c;用户可以迅速了解链上的交易情况&#xff0c;比如当前的TPS和Gas价格&#xff0c;也可以使用Digest来查看特定交易…

工厂安灯呼叫系统解决方案

在选择安灯呼叫系统之前&#xff0c;需要先了解自己的需求。不同的工厂可能有不同的需求&#xff0c;例如生产线的规模、生产过程中可能会出现的问题等。因此&#xff0c;选择安灯呼叫系统之前&#xff0c;需要先考虑自己的需求&#xff0c;以便选择到最适合自己的系统。要从多…

三十九、分布式事务、seata、

1、事务 事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列SQL操作&#xff0c;这些操作作为一个整体一起向系统提交&#xff0c;要么都执行、要么都不执行。 1.1 ACID事务的特点 原子性: 一致性&#xff1a;隔离性持久性 1.2 事务并发带来的问题 脏读 幻读 不可重复读 …

Mongodb Shell 常用操作命令

目录 一、启动与关闭mongodb服务 二、进入shell操作 三、常用shell命令 一、启动与关闭mongodb服务 启动:命令: ./mongod -config ../data/mongodb.conf 关闭命令: ./mongod -config ../data/mongodb.conf -shutdown 二、进入shell操作 命令:./mongo 三、常用shell命令 sh…

mysql查看实时执行的sql

MySQL默认不能实时查看执行的SQL语句&#xff0c;因为这会消耗一定的资源。 要开启这个功能&#xff0c;稍微配置一下&#xff0c;打开这个LOG记录就可以了。 查看开启情况 SHOW VARIABLES LIKE "general_log%";general_log值为OFF说明没有开启&#xff1a; 打开…

C++——内存管理+模块

作者&#xff1a;几冬雪来 时间&#xff1a;2023年5月19日 内容&#xff1a;C——内存管理模块 目录 前言&#xff1a; 1.new和delete操作自定义类型&#xff1a; operator new/delete&#xff1a; 定位new表达式&#xff08;placement-new&#xff09;&#xff1a; …

横向对比 11 种算法,多伦多大学推出机器学习模型,加速长效注射剂新药研发

内容一览&#xff1a;长效注射剂是解决慢性病的有效药物之一&#xff0c;不过&#xff0c;该药物制剂的研发耗时、费力&#xff0c;颇具挑战。对此&#xff0c;多伦多大学研究人员开发了一个基于机器学习的模型&#xff0c;该模型能预测长效注射剂药物释放速率&#xff0c;从而…

软件物料清单:打开软件资产黑匣子的关键钥匙

大家有没有遇到过&#xff0c;手机被免费召回维修的情况&#xff1f; 有些人可能遇到这样的问题&#xff0c;手机购买一段时间后&#xff0c;突然收到手机品牌官方发布的通知&#xff1a;听筒模块上的某个组件可能会发生故障&#xff0c;会出现拨打或接听电话时听筒发不出声音的…

接口自动化【五】(HandleRequests类的封装,及postman上下接口依赖的初步认识)

文章目录 前言一、封装发送请求的操作二、迷惑的知识点三、postman的全局变量机制总结 前言 所有的封装就是一种思想&#xff0c;这种思想能不能想到&#xff0c;其实跟写代码建立思维有很大的关系。 下面也是我学到的一种思想&#xff0c;其中对每个函数有解读。以及易错点的…

算法小课堂(九)分支限界法

一、概述 1.1概念 分支限界法是一种求解最优化问题的算法&#xff0c;常以广度优先或以最小耗费&#xff08;最大效益&#xff09;优先的方式搜索问题的解空间树。其基本思想是把问题的可行解展开&#xff0c;再由各个分支寻找最佳解。 在分支限界法中&#xff0c;分支是使用广…

Go 语言核心编程-环境入门篇

第 1 章 Golang 开山篇 1.1 Golang 的学习方向 Go 语言&#xff0c;我们可以简单的写成 Golang 1.2 Golang 的应用领域 1.2.1区块链的应用开发 1.2.2后台的服务应用 1.2.3云计算/云服务后台应用 1.3 学习方法的介绍 1.4 讲课的方式的说明 努力做到通俗易懂注重 Go 语言体系&…

【软件测试项目】湖南交警一网通测试计划_2.0正版

目录 一、引言 1.1 编写目的 1.2 项目背景 1.3 适用范围 1.4 专业术语 二、测试任务 2.1 测试范围 2.2 测试目标 2.3 参考文档 2.4 提交文档(交付件) 三、测试进度 四、测试资源 4.1 人力资源 4.2 环境资源 4.3 测试工具 五、测试策略 5.1 功能测试 5.2 压力…