SpringBoot多数据源实现方案

SpringBoot多数据源实现方案

现在很多项目的开发过程中,可能涉及到多个数据源,像读写分离的场景,或者因为业务复杂,导致不同的业务部署在不同的数据库上,那么这样的场景,我们应该如何在代码中简洁方便的切换数据源呢?分析这个需求,我们发现要做的事情无非两件

  1. 构建多个数据源
  2. 封装一个模块能实现动态的切换数据源,且数据源的切换代码应该尽量和业务进行解耦

构建多个数据源

构建多个数据源其实比较简单,和构建一个数据源是类似的。在SpringBoot中,只需要做三件事

  1. 将数据库的配置注册到配置文件中
  2. 选择一个数据库连接池来构建数据源,我们这里用阿里出品的Druid
  3. 选择一个orm框架来实现基本的sql,我们这里选用Mybatis

springboot配置文件

spring:datasource:master:url: jdbc:mysql://localhost:3306/db_masterusername: rootpassword: ******driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourceslave:url: jdbc:mysql://localhost:3306/db_slaveusername: rootpassword: Hxy@950504driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcemybatis:mapper-locations: classpath:mapper/**/*.xml

注册多个数据源

@Configuration
public class DataSourceConfig {@Bean(name = "masterDataSource")@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DruidDataSourceBuilder.create().build();}@Bean(name = "slaveDataSource")@ConfigurationProperties("spring.datasource.slave")public DataSource slaveDataSource() {return DruidDataSourceBuilder.create().build();}
}

动态切换数据源

spring提供的方案

关于动态切换数据源,spring给我们提供了一套解决方案,主要通过AbstractRoutingDataSource类实现,这个类是一个抽象类,每次和数据库的交互都会调用该类的getConnection() 方法获取数据库连接,而getConnection() 方法会调用determineCurrentLookupKey先选择一个正确的数据源,数据源如何选择呢?他的具体实现是,由我们开发人员提前将所有的数据源通过K-V的格式放到一个map中,V是具体的数据源,K是数据源的唯一标识。然后将这个map交给AbstractRoutingDataSource去管理,在需要路由的时候他会根据给定的K从map中匹配对应的数据源。那么K又怎么来呢?哪个接口应该用哪个key呢?AbstractRoutingDataSource给我们提供了一个抽象方法determineTargetDataSource(),供我们自定义实现key的确定逻辑。这个其实是对模板方法模式的典型应用,核心代码如下:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {// map结构,用来保存所有的数据源@Nullableprivate Map<Object, Object> targetDataSources;// 默认的数据源@Nullableprivate Object defaultTargetDataSource;@Overridepublic void afterPropertiesSet() {if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");}this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());this.targetDataSources.forEach((key, value) -> {Object lookupKey = resolveSpecifiedLookupKey(key);DataSource dataSource = resolveSpecifiedDataSource(value);this.resolvedDataSources.put(lookupKey, dataSource);});if (this.defaultTargetDataSource != null) {this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);}}@Overridepublic Connection getConnection() throws SQLException {return determineTargetDataSource().getConnection();}/*** getConnection()方法和determineTargetDataSource()方法定义了获取数据库连接,选择数据源的核心逻辑*/protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = determineCurrentLookupKey();DataSource dataSource = this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");}return dataSource;}/*** 根据key选择数据源,但是哪个接口用那个key,由用户自己决定,这就是模板方法模式*/@Nullableprotected abstract Object determineCurrentLookupKey();}

构建动态数据源

在了解了上述的基本原理后,我们就可以着手构建我们的动态数据源啦,首先自定义一个类继承AbstractRoutingDataSource,实现determineCurrentLookupKey()方法。

/*** 继承spring提供的多数据源路由类,初始化默认数据源和实现选择数据源的方法** @author HXY*/
public class DynamicDataSource extends AbstractRoutingDataSource {// 有参构造器,初始化所有的数据源和默认数据源public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> allDataSource) {super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(allDataSource);super.afterPropertiesSet();}// 实现抽象方法,定义我们获取K的逻辑@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSource();}
}

DataSourceContextHolder类使用ThreadLocal来存储当前线程使用的数据源名称。通过setDataSourceKey()方法设置数据源名称,通过getDataSourceKey()方法获取数据源名称,通过clearDataSourceKey()方法清除数据源名称。

这里用ThreadLocal的主要原因是为了做多并发线程隔离,比如同一时间可能会有很多请求并发进来,假设有10个请求,然后系统分配线程1处理请求1,请求1需要用mster数据源,线程2处理请求2,请求2需要用slave数据源。他们可能同时在进行,那么我们如何将这些请求需要的key做线程隔离呢,使之不互相影响呢?ThreadLocal就可以做到。

public class DataSourceContextHolder {private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();public static void setDataSource(String dataSourceKey) {CONTEXT_HOLDER.set(dataSourceKey);}public static String getDataSource() {return CONTEXT_HOLDER.get();}public static void release() {CONTEXT_HOLDER.remove();}
}
@Configuration
public class DataSourceConfig {@Bean(name = "masterDataSource")@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DruidDataSourceBuilder.create().build();}@Bean(name = "slaveDataSource")@ConfigurationProperties("spring.datasource.slave")public DataSource slaveDataSource() {return DruidDataSourceBuilder.create().build();}// DynamicDataSource要交给spring管理@Primary  // 一定要写,让DynamicDataSource被容器优先选择@Beanpublic DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,@Qualifier("slaveDataSource") DataSource slaveDataSource) {// 所有数据源放到一个map中,交给动态数据源管理Map<Object, Object> targetDataSources = new HashMap<>(2);targetDataSources.put(DataSourceEnum.MASTER.name(), masterDataSource);targetDataSources.put(DataSourceEnum.SLAVE.name(), slaveDataSource);// 默认数据源、所有数据源return new DynamicDataSource(masterDataSource, targetDataSources);}
}

通过切面将业务和数据源切换模块解耦

现在动态数据源切换的方案有了,那么如何能将每一个请求路由的到正确的数据源,而且将这些和业务无关的代码和业务进行解耦呢。是的,我们可以用aop,构建一个切面,在实现一个自定义注解,将注解标记在需要切换数据源的接口上,让每一个请求处理之前先去选择数据源,在处理业务逻辑,最后返回结果是不是就OK了?说干就干

/*** 自定义注解用来选择数据源** @author HXY* @since 1.0.0*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {DataSourceEnum key() default DataSourceEnum.MASTER;
}
public enum DataSourceEnum {MASTER,SLAVE,;
}
@Aspect
@Component
public class DynamicDataSourceAspect {// 用环绕通知拦截标记了DataSource注解的方法,方法执行前选择数据源,然后执行原来的方法,最后返回结果@Around("@annotation(dataSource)")public Object selectDataSource(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {try {String selectKey = dataSource.key().name();DataSourceContextHolder.setDataSource(selectKey);return joinPoint.proceed();} finally {// 请求处理完成后一定要及时释放ThreadLocal数据,否则会引起内存泄漏DataSourceContextHolder.release();}}
}

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

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

相关文章

Springboot中自定义拦截器

文章目录 拦截器实现白名单即API鉴权自定义拦截器注册拦截器 拦截器Service注入失效解决方式&#xff1a;获取用户真实IP地址 工具类 Spring Boot 中使用拦截器 参考&#xff1a;https://blog.csdn.net/taojin12/article/details/88342576?ops_request_misc%257B%2522request%…

【HTML】SVG实现炫酷的描边动画

前沿 今天闲来无事&#xff0c;看到Antfu大佬的个性签名&#xff0c;觉得还是非常炫酷的&#xff0c;于是也想要搞一个自己的个性签名用来装饰自己的门面&#xff0c;不过由于手写的签名太丑了&#xff0c;遂放弃。于是尝试理解原理&#xff0c;深入研究此等密法&#xff0c;终…

VLM多模态图像识别小模型UForm

参考:https://github.com/unum-cloud/uform https://huggingface.co/unum-cloud/uform-gen2-qwen-500m https://baijiahao.baidu.com/s?id=1787054120353641459&wfr=spider&for=pc demo:https://huggingface.co/spaces/unum-cloud/uform-gen2-qwen-500m-demo UF…

市场复盘总结 20240219

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 22% 最常用的…

Shiro-05-5 分钟入门 shiro 安全框架实战笔记

序言 大家好&#xff0c;我是老马。 前面我们学习了 web 安全之 Spring Security 入门教程 这次我们来一起学习下另一款 java 安全框架 shiro。 什么是Apache Shiro&#xff1f; Apache Shiro是一个功能强大且易于使用的Java安全框架&#xff0c;它为开发人员提供了一种直…

springboot拦截器配置

1、首先我们需要创建interceptor并实现springMVC中的HandlerInterceptor package com.example.cybg.web.intercepter;import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServl…

Redis 数据类型及其常用命令二(bitmap、geo、hyperloglog、bitfield、stream)

上文中我们介绍了Redis常使用的5中数据类型&#xff0c;对于一些特殊的场景&#xff0c;我们需要使用特殊的数据类型&#xff0c;本文将详细介绍5种特殊的数据类型。 1、bitmap 类型 用String类型作为底层数据结构实现的一种统计二值状态的数据类型。位图本质是数组&#xff0…

动态规划相关题目总结

221.最大正方形 设dp[i][j]为以点(i, j)为右下角的正方形最大边长&#xff0c;多画画图模拟模拟可以发现递推式dp[i][j] min(dp[i][j-1], dp[i-1][j-1], dp[i-1][j])1。 class Solution { public:int maximalSquare(vector<vector<char>>& matrix) {int n …

一文搞懂设计模式—适配器模式

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 微信公众号&#xff1a;Java随想录 文章目录 使用场景实现方式类适配器实现对象适配器实现 优缺点总结 适配器模式&#xff08;Adapter Pattern&#xff09;属于结构型模式&#xff0c;用于将一个类的接口转…

《剑指 Offer》专项突破版 - 面试题 45 和 46 : 二叉树最低层最左边的值和二叉树的右侧视图(C++ 实现)

目录 面试题 45 : 二叉树最低层最左边的值 面试题 46 : 二叉树的右侧视图 面试题 45 : 二叉树最低层最左边的值 题目&#xff1a; 如何在一棵二叉树中找出它最低层最左边节点的值&#xff1f;假设二叉树中最少有一个节点。例如&#xff0c;在下图所示的二叉树中最低层最左边…

Codeforces Round 924 (Div. 2)题解(A-D)

A - Rectangle Cutting 链接&#xff1a;A - Rectangle Cutting 思路 考虑横边和纵边&#xff0c;若为偶数&#xff0c;则从中间分开&#xff0c;重新组合为一个长方形&#xff0c;检测是否与原来的长方形一致。 代码 #include <bits/stdc.h> using namespace std;i…

logback实践

1:日志区分环境 2:debug info warn error日志文件不一样 3: 文件滚动日志 4:启动可带参数 --spring.profiles.activedev --log.levelinfo 5:可从配置文件中获取上下文参数 logback-spring.xml 放在 classpath 下面 <configuration scan"false" scanPer…

探秘OpenAI的神奇之作:Sora技术揭秘

探秘OpenAI的神奇之作&#xff1a;Sora技术揭秘 1. 引言 在当今科技快速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;正日益成为各个领域的关键技术。而在人工智能领域中&#xff0c;OpenAI公司一直以来都扮演着重要的角色。他们的最新创新——Sora技术&#x…

基于python的遥感影像灰色关联矩阵纹理特征计算

遥感影像纹理特征是描述影像中像素间空间关系的统计特征&#xff0c;常用于地物分类、目标识别和变化检测等遥感应用中。常见的纹理特征计算方式包括灰度共生矩阵&#xff08;GLCM&#xff09;、灰度差异矩阵&#xff08;GLDM&#xff09;、灰度不均匀性矩阵&#xff08;GLRLM&…

常见面试题:TCP的四次挥手和TCP的滑动窗口

说一说 TCP 的四次挥手。 挥手即终止 TCP 连接&#xff0c;所谓的四次挥手就是指断开一个 TCP 连接时。需要客户端和服务端总共发出四个包&#xff0c;已确认连接的断开在 socket 编程中&#xff0c;这一过程由客户端或服务端任意一方执行 close 来触发。这里我们假设由客户端…

unity学习(29)——GameInfo角色信息

1.把GameInfo.cs PlayerModel.cs Vector3.cs Vector4.cs PlayerStateConstans.cs GameState.cs依次粘到model文件夹中&#xff0c;此时项目没有错误&#xff0c;如下图所示&#xff1b; 对应处所修改的代码如下&#xff1a; case LoginProtocol.LOGIN_SRES://1 {Debug.Log(&qu…

考研查分,别再只知道研招网了!

查分时间基本已经敲定在2月26日左右了。倒计时7天&#xff01;每年查询分数的时候经常因为查询人数太多&#xff0c;进不去研招网&#xff0c;还有哪些方法可以查询分数呢&#xff1f; 我为大家整理了四种常用的查成绩方式&#xff0c;附带部分已公布查分时间院校名单。 一、…

Java学习心得感悟

在我踏入Java学习的道路之前&#xff0c;我对编程只是一知半解&#xff0c;对于代码的世界充满了好奇和向往。然而&#xff0c;当我真正开始学习Java时&#xff0c;我才意识到&#xff0c;学习Java不仅仅是学习一门编程语言&#xff0c;更是一种思维方式和解决问题的能力的培养…

【AI视野·今日Sound 声学论文速览 第四十九期】Wed, 17 Jan 2024

AI视野今日CS.Sound 声学论文速览 Wed, 17 Jan 2024 Totally 23 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers From Coarse to Fine: Efficient Training for Audio Spectrogram Transformers Authors Jiu Feng, Mehmet Hamza Erol, Joon Son Chung,…

Pandas Series Mastery: 从基础到高级应用的完整指南【第83篇—Series Mastery】

Pandas Series Mastery: 从基础到高级应用的完整指南 Pandas是Python中一流的数据处理库&#xff0c;它为数据科学家和分析师提供了强大的工具&#xff0c;简化了数据清理、分析和可视化的流程。在Pandas中&#xff0c;Series对象是最基本的数据结构之一&#xff0c;它为我们处…