【mysql】使用AbstractRoutingDataSource实现多数据源 与 获取mapper上注解
使用AbstractRoutingDataSource实现多数据源 与 获取mapper上注解
背景
随着业务发展速度越来越快,数据的增长也呈现倍数级别增长,数据库的压力,对于查询和写入等所有操作,都依赖于主库,其实有一些对于时效性要求不高的场景,无需使用主库查询,可以使用从库来分摊主库的压力,提升数据库集群整体的吞吐量
处理思路
其实处理思路共有两种
1. 我们创建两个数据源,不同的数据源扫描不同的包.
也就是查询从库或者查询主库,是通过不同的mapper文件物理隔离的,优势就是开发简单,快速搭建,不用担心侵入原有逻辑。缺点也是显而易见的,如果一个sql在某些场景下需要主库查询,另外场景需要从库查询,那么我们要写两套sql,如果有新增字段等ddl操作时,我们要改两遍sql,由于比较简单,本文不做描述。
2. 使用动态数据源配置
在多个数据源之上,抽象出来一个虚拟的动态数据源,等到执行sql的前一步,我们才会选择哪个数据源执行,优点是能够更精细化的管控sql执行,缺点的话就是开发稍微复杂些
动态数据源的执行逻辑
我们需要先了解下spring的AbstractRoutingDataSource ,spring提供了一套数据库路由方案,我们重写determineCurrentLookupKey 方法,将想要的数据库返回即可。
以spring boot 为例
一、 mysql的基本配置
-
首先配置主库和从库的数据源,这个是前提
@Bean(name = "mysqlDataSource") @Primary public DataSource mysqlDataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClass);dataSource.setUrl(url);dataSource.setUsername(user);dataSource.setPassword(password);dataSource.setMaxActive(300);dataSource.setKeepAlive(true);dataSource.setMaxWait(60000);dataSource.setValidationQuery("SELECT 'x'");dataSource.setMinEvictableIdleTimeMillis(300000);dataSource.setTestWhileIdle(true);dataSource.setTestOnBorrow(true);dataSource.setTestOnReturn(false);return dataSource; } /*** 从库主库数据源*/ @Bean(name = "mysqlSlaveDataSource") public DataSource mysqlSlaveDataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClass);dataSource.setUrl(url);dataSource.setUsername(readUser);dataSource.setPassword(passwordRead);dataSource.setMaxActive(300);dataSource.setKeepAlive(true);dataSource.setMaxWait(60000);dataSource.setValidationQuery("SELECT 'x'");dataSource.setMinEvictableIdleTimeMillis(300000);dataSource.setTestWhileIdle(true);dataSource.setTestOnBorrow(true);dataSource.setTestOnReturn(false);return dataSource; } -
配置动态数据源,将多数据源注入进动态数据源,并设置默认的数据源
@Bean(name = "dynamicDataSource") public DynamicDataSource dynamicDataSource(@Qualifier("mysqlDataSource") DataSource mysqlDataSource,@Qualifier("mysqlSlaveDataSource") DataSource mysqlSlaveDataSource) {Map<Object, Object> targetDataSources = Maps.newHashMap();targetDataSources.put(DataSourceEnum.MASTER, mysqlDataSource);targetDataSources.put(DataSourceEnum.SLAVE, mysqlSlaveDataSource);return new DynamicDataSource(mysqlDataSource, targetDataSources); } -
将动态数据源设置到sql的sessionFactory
@Bean(name = "mysqlSqlSessionFactory") @Primary public SqlSessionFactory masterSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dynamicDataSource);sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MysqlDataSourceConfig.MAPPER_LOCATION));// 配置org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();configuration.setMapUnderscoreToCamelCase(true);configuration.setDefaultStatementTimeout(30);configuration.addInterceptor(new MybatisUMPInterceptor());sessionFactory.setConfiguration(configuration);return sessionFactory.getObject(); }
二、 新增的配置
- 新增加库的枚举定义
package com.common.enums;/*** 数据源配置枚举** @author wangzym*/
public enum DataSourceEnum {/*** 使用主库*/MASTER(1, "主库"),/*** 使用从库*/SLAVE(2, "从库");/*** /*** 编码*/private Integer code;/*** 描述*/private String desc;DataSourceEnum(Integer code, String desc) {this.code = code;this.desc = desc;}public Integer getCode() {return this.code;}public String getDesc() {return this.desc;}public static DataSourceEnum getByCode(Integer code) {if (code == null) {return null;}for (DataSourceEnum dataSourceEnum : DataSourceEnum.values()) {if (dataSourceEnum.getCode().equals(code)) {return dataSourceEnum;}}return null;}
}
- 动态数据源的定义,需要继承spring的AbstractRoutingDataSource
package com.test.datasource;import com.alibaba.fastjson.JSON;
import com.test.SettleCheckException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.util.Map;/*** 动态切换数据源类** @author wangzym*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {//不符合配置条件直接中断启动流程if (defaultTargetDataSource == null || targetDataSources == null || targetDataSources.isEmpty()) {throw new SettleCheckException("默认数据源和可切换的数据源不允许为空!请检查 spring-config-datasource-druid.xml - dynamicDataSource 对应配置");}//设置默认数据源super.setDefaultTargetDataSource(defaultTargetDataSource);//设置用于切换的数据源super.setTargetDataSources(targetDataSources);//初始化本地的数据源记录super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {log.debug("当前线程使用SQL的数据库是:{}", JSON.toJSONString(DynamicDataSourceHolder.getDataSource()));return DynamicDataSourceHolder.getDataSource();}}
-
新增注解
package com.test.datasource;import com.test.DataSourceEnum;import java.lang.annotation.*;/*** 切换数据源注解,默认连接主库* 要在mapper上加此注解才可以* <p>*/ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface SwitchDataSource {DataSourceEnum name() default DataSourceEnum.MASTER; } -
拦截器改造需要拦截sql,此方法可以直接拦截sql执行,此方法要重点理解,是mybatis的拦截方案,其中Intercepts可以通过不同的类型,来进行不同的拦截,此处拦截所有执行的sql,直接获取mapper上的注解会获取不到,需要通过反射的方式,来获取,需要重点注意,此处不是本文的重点,有兴趣可以了解mybatis源码。
package com.test.interceptor;import com.test.DuccConstants; import com.test.enums.DataSourceEnum; import com.test.datasource.DynamicDataSourceHolder;@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),}) @Slf4j public class MybatisUMPInterceptor implements Interceptor {@Value("${test.current.env:pre}")private static String env;static {env = System.getProperty("current.env", "pre");log.info("当前切面的环境为:{}", env);}@Overridepublic Object intercept(Invocation invocation) throws Throwable {CallerInfo callerInfo = null;try {//1.处理数据源切换SwitchDataSource chooseDataSource = getSwitchDbAnnotation((MappedStatement) invocation.getArgs()[0]);if (null != chooseDataSource && DuccConstants.allowedMultiDb()) {//默认会使用MASTER数据源 如果开启事务了,而且当前数据源不是MASTER,会强制切换为MASTERDynamicDataSourceHolder.setDataSource(chooseDataSource.name());if (TransactionSynchronizationManager.isActualTransactionActive() && DataSourceEnum.SLAVE.equals(chooseDataSource.name())) {DynamicDataSourceHolder.setDataSource(DataSourceEnum.MASTER);}} else {DynamicDataSourceHolder.setDataSource(DataSourceEnum.MASTER);}//2.埋点监控Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];//ump key可以自定义String jKey = String.format("%s_MyBatis.%s", env, ms.getId());if (DynamicDataSourceHolder.getDataSource() != null && DynamicDataSourceHolder.getDataSource().equals(DataSourceEnum.SLAVE)) {jKey = jKey + "_slave";}callerInfo = Profiler.registerInfo(jKey, false, true);} catch (Exception ignore) {//ignorelog.warn("切面执行出现异常:{}", ignore.getStackTrace(), ignore);}try {return invocation.proceed();} catch (Throwable e) {try {if (null != callerInfo) {Profiler.functionError(callerInfo);}} catch (Exception ignore) {//ignore}throw e;} finally {try {if (null != callerInfo) {Profiler.registerInfoEnd(callerInfo);}} catch (Exception ignore) {//ignore}}}private SwitchDataSource getSwitchDbAnnotation(MappedStatement mappedStatement) {SwitchDataSource annotation = null;try {String id = mappedStatement.getId();String className = id.substring(0, id.lastIndexOf("."));String methodName = id.substring(id.lastIndexOf(".") + 1);final Method[] method = Class.forName(className).getMethods();for (Method me : method) {if (me.getName().equals(methodName) && me.isAnnotationPresent(SwitchDataSource.class)) {return me.getAnnotation(SwitchDataSource.class);}}} catch (Exception ex) {log.error("", ex);}return annotation;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {} } -
在mapper上打注解标记
@SwitchDataSource(name = DataSourceEnum.SLAVE) Long queryTaskOfShopCount(GeneralFeeDetailDTO generalFeeDetailDTO);至此,mysql 动态路由到指定数据库就讲解完成。
相关文章:
【mysql】使用AbstractRoutingDataSource实现多数据源 与 获取mapper上注解
使用AbstractRoutingDataSource实现多数据源 与 获取mapper上注解 背景 随着业务发展速度越来越快,数据的增长也呈现倍数级别增长,数据库的压力,对于查询和写入等所有操作,都依赖于主库,其实有一些对于时效性要求不高…...
希沃冰点还原
要取消希沃冰点还原,可以按照以下步骤进行: 打开希沃冰点还原的应用或程序。 在应用或程序的界面上,寻找设置选项或菜单。 点击或选择设置选项或菜单,进入设置界面。 在设置界面上,查找“取消”或“停止”等相关选项…...
Hadoop服务端口号、Spark端口号、Hive端口号以及启动命令
文章目录 1. 服务端口号1.1 Hadoop相关的服务端口号1.2 Spark相关的服务端口号1.3 Hive的连接端口 2. 服务启动指令 1. 服务端口号 1.1 Hadoop相关的服务端口号 HDFS的web页面访问端口 9870HDFS 的程序访问端口 8020Yarn的访问端口 8088历史日志访问端口 19888 1.2 Spark相关…...
【C++】--类和对象(3)
🤑个人主页: 起名字真南 🤑个人专栏:【数据结构初阶】 【C语言】 【C】 目录 1 深入构造函数2 类型转换3 static成员4 友元函数5 内部类6 匿名对象 1 深入构造函数 之前我们实现构造函数的时候,初始化成员变量都是在函数体内赋值,…...
国外电商系统开发-运维系统文件上传-高级上传
如果您要上传文件到10台服务器中,有3台服务器的路径不是一样的,那么在这种情况下您就可以使用本功能,单独执行不一样的路径 点击【高级】上传...
【MongoDB】mongodb | 部署 | 常用命令
一、概述 基于mongodb的tcp连接无数据上报,服务器强踢监测。 物联网项目,tcp协议,基于4G卡,设备由于某些原因会断开重连,但是tcp没有断开,导致tcp持续累加,浪费资源。 建立机制: 当t…...
【Chrome浏览器插件--资源嗅探猫抓】
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、资源嗅探插件---猫抓二、使用步骤总结 一、资源嗅探插件—猫抓 猫抓是一个浏览器插件,可以检测当前网页中的一些资源文件,可设置嗅探的…...
2.4Mybatis——缓存机制
2.4Mybatis——缓存机制 缓存配置一二级缓存一级缓存二级缓存 合集总览:Mybatis框架梳理 讲真,Mybatis缓存这块的记忆已经模糊了。刚好此时写测试用例出现一个BUG,就以这个问题作为切入点来梳理一下。 Testpublic void test(){Address ad…...
移动技术开发:文件的读取
1 实验名称 文件的读写 2 实验目的 掌握Android中读写文件的实现方法。 3 实验源代码 布局文件代码: <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android&quo…...
Linux 中的 Makefile 伪目标详解
在 Linux 环境中,Makefile 是构建项目的重要工具,它通过定义规则,指导 make 工具如何编译和链接程序。通常我们会在 Makefile 中定义目标(target),这些目标通常对应文件名。然而,有一种特殊类型…...
Java基础(中)
变量 成员变量与局部变量的区别 语法形式:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控…...
Leetcode热题100-200 岛屿数量
Leetcode热题100-200 岛屿数量 1. 题目描述2. 代码实现1. dfs算法2. bfs算法 1. 题目描述 200 岛屿数量 2. 代码实现 1. dfs算法 class Solution { public:int numIslands(vector<vector<char>>& grid) {int m grid.size(), n grid[0].size();int res 0…...
大数据新视界 --大数据大厂之 GraphQL 在大数据查询中的创新应用:优化数据获取效率
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
swift使用代码结构解析
多模态模型的训练llamafactory也可以训练,但是总的来说,llamafactory对多模态模型的支持还是不太多,ms-swift支持的多模态模型更多,因此有时候去找框架是否够支持相应的模型时会有所困难,所以对这些框架的代码也要稍微…...
五、Python基础语法(程序的输入和输出)
一、输入 输入:输入就是获取键盘输入的数据,使用input()函数。代码会从上往下执行,当遇到input()函数,就会暂停执行,输入内容后,敲回车键,表示本次的输入结束。input函数得到的数据类型都是字符…...
【C语言】常见概念
文章目录 库函数关键字字符和ASCll编码字符串与\0转义字符语句和语句分类注释 库函数 为了不再重复实现常见的代码,让程序员提升开发效率,C语言标准规定了一组函数,这些函数再由不同的编译器厂商根据标准进行实现,提供给程序员使…...
Electron应用创建和打包
一、创建项目目录 创建NodeJs项目目录,项目有关的文件、依赖包都将在本目录创建和安装。 mkdir hello_electron & cd hello_electronCMD执行以上命令将在用户目录下创建hello_electron并进入该目录。当然也可以手动在任何地方创建目录,cmd中cd 路径…...
代码随想录算法训练营第五六天| 99. 岛屿数量 100. 岛屿的最大面积
今日任务 99. 岛屿数量 深度搜搜 99. 岛屿数量 广度搜索 100. 岛屿的最大面积 99. 岛屿数量 题目链接: 99. 岛屿数量 import java.util.Scanner;public class Main {public static int[][] dir {{0, 1},{1, 0},{-1, 0},{0, -1}};public static void dfs(boolean…...
图解 微信开发者工具 小程序源码 调试、断点标记方法 , 微信小程序调试器,真机调试断点调试方法,小程序网络API请求调试方法 总结
在我们使用微信开发者工具进行微信小程序开发的时候,在这个微信开发者工具的代码编辑框里面我们是无法像使用vscode, idea等IDE工具时那样直接对代码打断点进行调试, 原因是小程序实际上他就是一个web浏览器应用的包装, 在其内部使用的还是类似chrome的…...
注释,换行,控制台输入输出,命名空间,省略return语句
注释 1.单行注释 // 2.多行注释 /*注释内容*/ 解释代码的作用;注释多余内容;注释不会影响代码执行 换行 \nstd::endl 控制台输入输出 输出123456 可一次性输出多个 std::cout<<"123456"//示例获取控制台输入内容,存储在…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
