SpringBoot 手动实现动态切换数据源 DynamicSource (上)

大家好,我是此林。

在实际开发中,经常可能遇到在一个SpringBoot Web应用中需要访问多个数据源的情况。

下面来介绍一下多数据源的使用场景、底层原理和手动实现。

一、 多数据源经典使用场景

场景一:业务复杂,数据量过大

1. 业务初期,开发的SpringBoot应用只需要访问一个数据库。

2. 随着业务的复杂度和数据量的不断增长,一台Mysql服务器容量可能存不下了,或者说业务复杂需要对接多个数据源。

3. 此时,一个SpringBoot 需要访问多个数据源。

上文所述也就是应用没有拆分,数据库进行了拆分。

有人可能会说,为什么不把应用也拆分成微服务,每个服务可以使用自己的独立的数据库。

答:要看实际业务,SpringBoot拆分成微服务也需要成本。

场景二:读写分离

虽然一个SpringBoot应用使用一个数据源,但为了保证Mysql的性能和高可用性,采用了Mysql主从集群的方式部署,主数据库只进行写操作,从数据库负责读操作。

此时,需要进行数据源的动态切换。

常用中间件:ShardingSphere、MyCat。

Mysql主从集群、读写分离,解决了以下问题:

1. 提高并发量,因为写锁会阻塞读操作。

2. 保证高可用性,数据备份。

二、动态切换数据源的底层原理 

我们先来想下,要用Spring 去操作mysql,配置流程。

比如我们执行了 userMapper.insert(user) ,见下图。

简而言之,ORM框架底层通过Spring-jdbc拿到我们配置的DataSource,再调用getConnection() 方法拿到数据库connection连接。

那么,Mybatis 通过 connection 就可以对数据库进行 CRUD 操作了。

观察发现,要实现动态数据源切换,我们能配置的只有DataSource这个扩展点。

所以,更改如下。

这里我们自定义了DynamicDataSource类,实现了DataSource接口,重写了getConnection() 方法。

读操作(用R标识) ,写操作(用W标识)。

根据不同的业务标识(R 或 W),来返回不同的注入的datsource bean(最左侧)。

因为前面也说了,只要执行了userMapper.insert() 方法,那么它底层就会先去 getConnection 得到数据库连接,才能对mysql 进行 CRUD操作。

三、手动实现

pom.xml

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.24</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency></dependencies>

application.yml

spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedatasource1:url: jdbc:mysql://localhost:3306/datasource1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverdatasource2:url: jdbc:mysql://localhost:3307/datasource2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver

DataSourceConfig.java

@Configuration
public class DataSourceConfig {@Bean(name = "dataSource1")@ConfigurationProperties(prefix = "spring.datasource.datasource1")public DataSource dataSource1(){return DruidDataSourceBuilder.create().build();}@Bean(name = "dataSource2")@ConfigurationProperties(prefix = "spring.datasource.datasource2")public DataSource dataSource2(){return DruidDataSourceBuilder.create().build();}
}

DynamicDataSource.java

@Component
@Primary // 将该Bean设置为主要注入Bean
public class DynamicDataSource implements DataSource, InitializingBean {// 当前使用的数据源标识public static ThreadLocal<String> name = new ThreadLocal<>();// 写@Autowired@Qualifier("dataSource1")DataSource dataSource1;// 读@Autowired@Qualifier("dataSource2")DataSource dataSource2;@Overridepublic Connection getConnection() throws SQLException {if (name.get().equals("R")) {return dataSource1.getConnection();} else {return dataSource2.getConnection();}}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic boolean isWrapperFor(Class<?> iface) throws SQLException {return false;}@Overridepublic PrintWriter getLogWriter() throws SQLException {return null;}@Overridepublic void setLogWriter(PrintWriter out) throws SQLException {}@Overridepublic void setLoginTimeout(int seconds) throws SQLException {}@Overridepublic int getLoginTimeout() throws SQLException {return 0;}@Overridepublic Logger getParentLogger() throws SQLFeatureNotSupportedException {return null;}@Overridepublic void afterPropertiesSet() throws Exception {name.set("W");}
}

由于我们注入了三个DataSource:datasource1、datasource2、DynamicDatasource

1. Spring 首先根据DataSource类型去IOC 容器中找,找到了三个DataSource。

2. 找到了后再根据beanName = "datasource" 去找

3. 我们这里没有bean的名字叫datasource的,所以Spring不知道使用哪个DataSource。

解决:在DynamicDatasource类上加@Primary,将其作为主要的Bean优先注入使用。

(即:当出现相同的DataSource类型,优先使用DynamicDatasource)

测试:

通过浏览器访问,查看mysql数据库检验是否进行了对应的数据源切换即可。

当然,这只是粗糙的实现了以下动态数据源的切换,为了讲明白原理,简化了很多步骤。

后续会出SpringBoot 手动实现动态切换数据源 DynamicSource (中)、SpringBoot 手动实现动态切换数据源 DynamicSource (下),讲述进阶版、多数据源事务管理、及主流框架使用,持续更新!

关注我吧,我是此林,带你看不一样的世界!

更新后续:

2024.12.12

SpringBoot 手动实现动态切换数据源 DynamicSource (中)-CSDN博客

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

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

相关文章

SQLServer定时备份到其它服务器上

环境 Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) SQL Server Management Studio 15.0.18384.0 SQL Server 管理对象 (SMO) 16.100.46367.54 Microsoft .NET Framework 4.0.30319.42000 本地操作系统&#xff08;SQLServer环境&#xff09; Windows 11 远程操作系统…

PaddleOCR模型ch_PP-OCRv3文本检测模型研究(二)颈部网络

上节研究了PaddleOCR文本检测v3模型的骨干网&#xff0c;本文接着研究其颈部网络。 文章目录 研究起点残注层颈部网络代码实验小结 研究起点 摘取开源yml配置文件&#xff0c;摘取网络架构Architecture中颈部网络的配置如下 Neck:name: RSEFPNout_channels: 96shortcut: True可…

IEEE T-RO 软体机器人手指状态估计实现两栖触觉传感

摘要&#xff1a;南方科技大学戴建生院士、林间院士、万芳老师、宋超阳老师团队近期在IEEE T-RO上发表了关于软体机器人手指在两栖环境中本体感知方法的论文。 近日&#xff0c;南方科技大学戴建生院士、林间院士、万芳老师、宋超阳老师团队在机器人顶刊IEEE T-RO上以《Propri…

Java 环境变量配置全解析

一、Java 环境变量配置的重要性 程序运行基础 确保 Java 应用能够在操作系统中被正确识别和启动&#xff0c;否则将无法执行任何 Java 代码。为 Java 运行时环境&#xff08;JRE&#xff09;提供必要的路径信息&#xff0c;使其能找到所需的类库和资源。使得系统可以定位到 Jav…

AutoMQ x Iceberg SG Meetup: Table Topic 新特性统一流与分析首次公开

活动介绍 Apache Iceberg 已经成为数据湖表格式的事实标准&#xff0c;为存储在S3上的数据提供了统一的格式。随着 AWS 在 2024 年的 re:invent 大会上宣布支持 Iceberg Table 的 S3 Table 新特性&#xff0c;Iceberg 再一次受到全球开发者的关注和讨论。AutoMQ 是基于云优先理…

java-多线程的创造方式(自用笔记)

方式一&#xff1a;继承父类thread 1、定义一个子类&#xff0c;继承父类thread 2、重写父类的run方法 3、创建线程类的“对象” 4、启动线程 package week14;public class ThreadDemo {public static void main(String[] args) {//3、创建线程类的“对象”Thread t1 new…

【CSS in Depth 2 精译_071】11.4 思考字体颜色的对比效果 + 11.5 本章小结

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 11 章 颜色与对比】 ✔️ 11.1 通过对比进行交流 11.1.1 模式的建立11.1.2 还原设计稿 11.2 颜色的定义 11.2.1 色域与色彩空间11.2.2 CSS 颜色表示法 11.2.2.1 RGB…

MySQL ON DUPLICATE KEY UPDATE影响行数

目录 分析为什么Updates返回7 总结 数据库更新日志如下 insertOrUpdateList|> Preparing: INSERT INTO clue_user_tag (vuid, tag_id, tag_type, content) VALUES (?, ?, ?, ?) , (?, ?, ?, ?) , (?, ?, ?, ?) , (?, ?, ?, ?) ON DUPLICATE KEY UPDATE …

spring cloud之Bus回顾

最开始学习Bus时&#xff0c;为了刷新Config配置&#xff0c;后面很少用了&#xff0c;发现这个它的用途很大的&#xff0c;spring cloud不是想进能进的。 server 下面这里其实就是重复了下Config-server流程&#xff0c;略有改动 1、pom <dependency><groupId>…

Java中的Stream

1. 什么是 Stream&#xff1f; Stream 是 Java 8 引入的一种新方式&#xff0c;目的是帮助我们更简洁、更高效地处理集合&#xff08;如 List、Set、Map 等&#xff09;。你可以把 Stream 想象成一条“流水线”&#xff0c;数据就像是流水线上的原材料&#xff0c;经过流水线的…

使用 Kubernetes 部署 Redis 主从及 Sentinel 高可用架构(未做共享存储版)

文章目录 使用 Kubernetes 部署 Redis 主从及 Sentinel 高可用架构Redis 主从架构部署 (1.yaml)Redis Sentinel 部署 (2.yaml)Sentinel 服务暴露 (3.yaml)部署步骤总结 使用 Kubernetes 部署 Redis 主从及 Sentinel 高可用架构 本文将详细介绍如何在 Kubernetes 中部署 Redis …

ElasticSearch常见的索引_集群的备份与恢复方案

方案一&#xff1a;使用Elasticsearch的快照和恢复功能进行备份和恢复。该方案适用于集群整体备份与迁移&#xff0c;包括全量、增量备份和恢复。 方案二&#xff1a;通过reindex操作在集群内或跨集群同步数据。该方案适用于相同集群但不同索引层面的迁移&#xff0c;或者跨集…

GauHuman阅读笔记【3D Human Modelling】

笔记目录 1. 基本信息2. 理解(个人初步理解,随时更改)3. 精读SummaryResearch Objective(s)Background / Problem StatementMethod(s)EvaluationConclusionReferences1. 基本信息 题目:GauHuman: Articulated Gaussian Splatting from Monocular Human Videos时间:2023.12…

onlyoffice 容器配置修改后制作镜像导出以及上传到 dockerhub

1.将容器制作成新的镜像 docker commit -p -a "xxx" -m "zh-cn-20-100" onlyoffice ooffice:7.1.1.23docker commit: 这是 Docker 中用于创建新镜像的命令。 -p: 这个选项用于在提交之前暂停容器的运行。这可以确保数据的完整性&#xff0c;因为容器在提交…

CentOS7环境安装php

直接安装 yum -y install php CentOS7默认安装是php5&#xff0c;现在php已有8.3版本 先查看php -v 版本 如果是低版本&#xff0c;可以删除 yum remove php yum remove php-fpm yum remove php-common 一、添加REMI存储库 yum install epel-release yum install -y …

ByteByteGo-Top 9 HTTP Request Methods 9种HTTP请求方法

更详细准确的描述直接参考 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/CONNECT ​​​​​本片文章出自 EP85: Top 9 HTTP Request Methods - ByteByteGo Newsletter http request methods 1&#xff0c;GET 从服务器获取资源。 具有幂等性&#xff08;idempo…

HarmonyOS:多线程并发-Worker

Worker主要作用是为应用程序提供一个多线程的运行环境&#xff0c;可满足应用程序在执行过程中与宿主线程分离&#xff0c;在后台线程中运行一个脚本进行耗时操作&#xff0c;极大避免类似于计算密集型或高延迟的任务阻塞宿主线程的运行。具体接口信息及使用方法详情请见Worker…

Linux 升级 openssh 方法步骤

目录 一&#xff1a;OpenSSH 介绍二&#xff1a;升级 OpenSSH1、下载源码包2、查看当前安装位置3、上传源码包4、生成Makefile文件5、编译并安装6、重启ssh服务7、检查版本信息 一&#xff1a;OpenSSH 介绍 OpenSSH最初是作为OpenBSD系统项目的一部分而开发的&#xff0c;后面…

ES语句——DSL(kibana语句)

一、查询操作 查看当前索引的数据结构 _mapping Get ai-open-log*/_mapping 查询当前索引下的文档数以及分片信息 _count Get ai-open-log*/_count { "count": 12345, //当前索引下的文档总数 "_shards": { //分片信息 "total&…

docker开启远程访问

1、编辑docker.server文件 vi /usr/lib/systemd/system/docker.service 找到 [Service] 节点&#xff0c;修改 ExecStart 属性&#xff0c;增加 -H tcp://0.0.0.0:2375 ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/containerd.sock -H tcp://0.0.0.0:2…