根据租户id切换数据源

花了半天时间,使用spring-boot实现动态数据源,切换自如

  在一个项目中使用多个数据源的情况很多,所以动态切换数据源是项目中标配的功能,当然网上有相关的依赖可以使用,比如动态数据源,其依赖为,

<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.1</version>
</dependency>

  今天,不使用现成的API,手动实现一个动态数据源。

一、环境及依赖

springboot、mybatis-plus的基础上实现动态数据源切换,

springboot:2.3.3.RELEASE

mybatis-plus-boot-starter:3.5.0

mysql驱动:8.0.32

除了这些依赖外没有其他的,目标是动态切换数据源。

二、实现思路

  先来看下,单数据源的情况。

  在使用springboot和mybatis-plus时,我们没有配置数据源(DataSource),只配置了数据库相关的信息,便可以连接数据库进行数据库的操作,这是为什么呐。其实是基于spring-boot的自动配置,也就是autoConfiguration,在自动配置下有DataSourceAutoConfiguration类,该类会生成一个数据源并注入到spring的容器中,这样就可以使用该数据源提供的连接,访问数据库了。

  感兴趣的小伙伴可以了解下这个类的具体实现逻辑。

  要实现多数据源,并且可以自动切换。那么肯定就不能再使用DataSourceAutoConfigurtation了,因为它只能产生一个数据源,多个数据源要怎么办,spring提供了AbstractRoutingDataSource类,该类是一个抽象类,仅有一个抽象方法需要实现

Determine the current lookup key. This will typically be implemented to check a thread-bound transaction context.
Allows for arbitrary keys. The returned key needs to match the stored lookup key type, 
as resolved by the resolveSpecifiedLookupKey method.
@Nullable
protected abstract Object determineCurrentLookupKey();

可以根据该类实现一个动态数据源。好了,现在了解了实现思路,开始实现一个动态数据源,要做以下的准备工作。

1、配置文件;

2、自定义动态数据源;

2.1、配置文件

由于是多数据源,那么在配置文件中肯定是多个配置,不能再是一个数据库的配置了,这里使用两个mysql的配置进行演示,

#master 默认数据源
spring:datasource:master:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: 123456
#slave 从数据源slave:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test2?serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: 123456

这里使用了一个master一个slave两个数据源配置,其地址是一致的,但数据库示例不一样。 有了数据源的信息下一步要实现自己的数据源,

2.2、自定义动态数据源

  前边说,spring提供了AbstractRoutingDataSource类可以实现动态数据源,看下实现。

DynamicDatasource.java

package com.wcj.my.config.dynamic.source;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** 动态数据源* @date 2023/6/8 19:18*/
public class DynamicDatasource extends AbstractRoutingDataSource {/*** Determine the current lookup key. This will typically be* implemented to check a thread-bound transaction context.* <p>Allows for arbitrary keys. The returned key needs* to match the stored lookup key type, as resolved by the* {@link #resolveSpecifiedLookupKey} method.*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDatasourceHolder.getDataSource();}
}

这里的determineCurrentLookupKey方法,需要返回一个数据源,也就是说返回一个数据源的映射,这里返回一个DynamicDatasourceHolder.getDataSource()方法的返回值,DynamicDatasourceHolder是一个保存多个数据源的地方,

DynamicDatasourceHolder.java

package com.wcj.my.config.dynamic.source;import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;/*** @date 2023/6/8 19:42*/
public class DynamicDatasourceHolder {//保存数据源的映射private static Queue<String> queue = new ArrayBlockingQueue<String>(1);public static String getDataSource() {return queue.peek();}public static void setDataSource(String dataSourceKey) {queue.add(dataSourceKey);}public static void removeDataSource(String dataSourceKey) {queue.remove(dataSourceKey);}
}

该类很简单,使用一个队列保存数据源的映射,提供获取/设置数据源的方法。

这里使用ThreadLocal类更合适,这样可以实现线程的隔离,一个请求会有一个线程来处理,保证每隔线程使用的数据源是一样的。

到现在为止依旧没有出现如何创建多数据源,下面就来了,不着急。

DynamicDatasourceConfig.java

package com.wcj.my.config.dynamic.source;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @date 2023/6/8 19:51*/
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDatasourceConfig {@Bean("master")@ConfigurationProperties(prefix = "spring.datasource.master")public DataSource masterDatasource(){return DataSourceBuilder.create().build();}@Bean("slave")@ConfigurationProperties(prefix = "spring.datasource.slave")public DataSource slaveDatasource(){return DataSourceBuilder.create().build();}@Bean@Primarypublic DataSource dataSource(){Map<Object, Object> dataSourceMap = new HashMap<>(2);dataSourceMap.put("master", masterDatasource());dataSourceMap.put("slave", slaveDatasource());DynamicDatasource dynamicDatasource=new DynamicDatasource();dynamicDatasource.setTargetDataSources(dataSourceMap);dynamicDatasource.setDefaultTargetDataSource(masterDatasource());return dynamicDatasource;}
}

首先,在该类上有个一个@Configuration注解,标明这是一个配置类;

其次,有一个@EnableAutonConfiguration注解,该注解中有个数组类型的exclude属性,排除不需要自动配置的类,这里排除的是当然就是DataSourceAutoConfiguration类了;因为下面会自动生成数据源,不需要自动配置了;

然后,在类中是标有@Bean的方法,这些方法便是生成数据源类,且映射为”master“、”slave“,可以有多个。使用的是DataSourceBuilder类帮助生成;

最后,生成一个DynamicDatasource,且标有@Primary注解,这里需要设置”master“、”slave“两个映射代表的数据源;

这样便向spring容器中注入了三个数据源,分别是”master“、”slave“代表的数据源,他们是需要实际使用的数据源。还有一个是DynamicDatasource,提供数据源的设置。这三个都是DataSource的子类。

三、使用多数据源

  上面已经完成了多数据源的配置,下面看怎么使用吧,还记得DynamicDatasourceHolder类中有set/get方法吗,就是使用这个类提供的方法,

UserSerivce.java

package com.wcj.my.service;import com.wcj.my.config.dynamic.source.DynamicDatasourceHolder;
import com.wcj.my.dto.UserDto;
import com.wcj.my.entity.User;
import com.wcj.my.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @date 2023/6/8 15:19*/
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;/**默认使用master数据源*/public boolean saveUser(UserDto userDto) {User user = new User();user.setUName(userDto.getName());user.setUCode(userDto.getCode());user.setUAge(userDto.getAge());user.setUAddress(userDto.getAddress());int num = userMapper.insert(user);if (num > 0) {return true;}return false;}/***使用slave数据源*/public boolean saveUserSlave(UserDto userDto) {DynamicDatasourceHolder.setDataSource("slave");User user = new User();user.setUName(userDto.getName());user.setUCode(userDto.getCode());user.setUAge(userDto.getAge());user.setUAddress(userDto.getAddress());int num = userMapper.insert(user);DynamicDatasourceHolder.removeDataSource("slave");if (num > 0) {return true;}return false;}
}

  上面的service层方法在调用dao层方法的时候,使用DynamicDatasourceHolder.setDataSource()方法设置了需要使用的数据源, 通过这样的方式便可以实现动态数据源了。

  不知道,小伙伴们有没有感觉到,这样每次在调用方法的时候都需要设置数据源是不是很麻烦,有没有一种更方面的方式,比如说注解。

四、动态数据源注解@DDS

   现在来实现一个动态数据源的注解来代替上面的每次都调用DynamicDatasourceHolder.setDataSource()方法来设置数据源。

  先看下,@DDS注解的定义

DDS.java

package com.wcj.my.config.dynamic.source.aspect;import org.springframework.stereotype.Component;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/**动态数据源的注解* 用在类和方法上,方法上的优先级大于类上的* 默认值是master* @date 2023/6/9 16:19*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DDS {String value() default "master";
}

注解@DDS使用在类和方法上,切方法上的优先级大于类上的。有一个value的属性,指明使用的数据源,默认是”master“。

实现一个切面,来切@DDS注解

 DynamicDatasourceAspect.java

package com.wcj.my.config.dynamic.source.aspect;import com.wcj.my.config.dynamic.source.DynamicDatasourceHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.util.Objects;/*** 动态数据源切面* @date 2023/6/9 16:23*/
@Aspect
@Component
public class DynamicDatasourceAspect {/*** 切点,切的是带有@DDS的注解*/@Pointcut("@annotation(com.wcj.my.config.dynamic.source.aspect.DDS)")public void dynamicDatasourcePointcut(){}/*** 环绕通知* @param joinPoint* @return* @throws Throwable*/@Around("dynamicDatasourcePointcut()")public Object around(ProceedingJoinPoint joinPoint)throws Throwable{String datasourceKey="master";//类上的注解Class<?> targetClass=joinPoint.getTarget().getClass();DDS annotation=targetClass.getAnnotation(DDS.class);//方法上的注解MethodSignature methodSignature=(MethodSignature)joinPoint.getSignature();DDS annotationMethod=methodSignature.getMethod().getAnnotation(DDS.class);if(Objects.nonNull(annotationMethod)){datasourceKey=annotationMethod.value();}else{datasourceKey=annotation.value();}//设置数据源DynamicDatasourceHolder.setDataSource(datasourceKey);try{return joinPoint.proceed();}finally {DynamicDatasourceHolder.removeDataSource(datasourceKey);}}
}

 这样一个动态数据源的注解便可以了,看下怎么使用,

UserServiceByAnnotation.java

package com.wcj.my.service;import com.wcj.my.config.dynamic.source.DynamicDatasourceHolder;
import com.wcj.my.config.dynamic.source.aspect.DDS;
import com.wcj.my.dto.UserDto;
import com.wcj.my.entity.User;
import com.wcj.my.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @date 2023/6/8 15:19*/
@Service
public class UserServiceByAnnotation {@Autowiredprivate UserMapper userMapper;@DDS("master")public boolean saveUser(UserDto userDto){User user=new User();user.setUName(userDto.getName());user.setUCode(userDto.getCode());user.setUAge(userDto.getAge());user.setUAddress(userDto.getAddress());int num=userMapper.insert(user);if(num>0){return true;}return false;}@DDS("slave")public boolean saveUserSlave(UserDto userDto){User user=new User();user.setUName(userDto.getName());user.setUCode(userDto.getCode());user.setUAge(userDto.getAge());user.setUAddress(userDto.getAddress());int num=userMapper.insert(user);if(num>0){return true;}return false;}
}

使用起来很简单,在需要切换数据源的方法或类上使用@DDS注解即可,使用value来改变数据源就好了。

五、动态数据源的原理

  很多小伙伴可能和我有一样的疑惑,使用DynamicDatasourceHolder.setDataSource或@DDS就可以设置数据源了,是怎么实现的,下面分析下,我们指定dao层的Mapper其实是一个代理对象,其会使用mybatis中的sqlSessionTempalte进行数据库的操作,在sqlSessionTemplate中会使用DefaultSqlSession对象,最终会使用DataSource,而使用了动态数据源的对象中会注入一个DynamicDataSource,在进行数据库操作时最终会获得一个数据库连接,这里便会使用DynamicDataSource获得一个连接,由于它继承了AbstractRoutingDataSource类,看下其getConnection方法,

@Overridepublic Connection getConnection() throws SQLException {return determineTargetDataSource().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;}

看上面的注释,determineCurrentLookupkey()方法便是在DynamicDatasource类中进行了实现,从而实现了动态设置数据源的目的。

六、总结 

本文动手实现了一个动态数据源,并切提供了注解的方式,主要有以下几点

1、继承AbstractRoutingDataSource类的determineCurrentLookupkey()方法,动态设置数据源;

2、取消DataSourceAutoConfiguration的自动配置,手动向spring容器中注入多个数据源;

3、基于@DDS注解动态设置数据源;

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

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

相关文章

探索营销系统业务架构的设计与应用

随着市场竞争的日益激烈和消费者需求的不断变化&#xff0c;营销系统作为企业营销管理的重要组成部分&#xff0c;扮演着至关重要的角色。本文将深入探讨营销系统业务架构的设计与应用&#xff0c;从客户关系管理、营销活动管理、数据分析和智能化服务等方面进行全面解析&#…

Innodb Buffer Pool缓存机制(四)预读与Mysql改进的LRU策略

一、什么是预读 InnoDB提供了预读(read ahead)。所谓预读&#xff0c;就是InnoDB认为执行当前的请求可能之后会读取某些页面&#xff0c;就预先把它们加载到Buffer Pool中。根据触发方式的不同&#xff0c;预读又可以细分为下边两种&#xff1a; 1.1 线性预读 InnoDB提供了一…

掘金AI商战宝典-高阶班:如何用AI制作视频(11节视频课)

课程下载&#xff1a;掘金AI商战宝典-高阶班&#xff1a;如何用AI制作视频(11节视频课)-课程网盘链接提取码下载.txt资源-CSDN文库 更多资源下载&#xff1a;关注我。 课程目录&#xff1a; 1-第一讲用AI自动做视频(上)_1.mp4 2-第二讲用AI自动做视频(中)_1.mp4 3-第四讲A…

阿里云邮件推送服务配置教程:怎么做批发?

阿里云邮件推送的API配置步骤&#xff1f;配置教程有哪些步骤&#xff1f; 阿里云邮件推送服务凭借其高并发、稳定性强和安全性高等特点&#xff0c;成为众多企业的首选。Aok将详细介绍如何使用阿里云邮件推送服务进行批发配置&#xff0c;并简要提及AokSend的优势。 阿里云邮…

UE4_环境_材质函数

学习笔记&#xff0c;不喜勿喷&#xff0c;欢迎指正&#xff0c;侵权立删&#xff01; 1、建立材质函数Distance_Fun&#xff0c;勾选公开到库。 2、添加函数输入节点FunctionInput&#xff0c; 这个输入我们想作为混合材质属性BlendMaterialAttributes的alpha输入节点&#x…

手撸 串口交互命令行 及 AT应用层协议解析框架

在嵌入式系统开发中&#xff0c;命令行接口&#xff08;CLI&#xff09;和AT命令解析是常见的需求。CLI提供了方便的调试接口&#xff0c;而AT命令则常用于模块间的通信控制。本文将介绍如何手动实现一个串口交互的命令行及AT应用层协议解析框架&#xff0c;适用于FreeRTOS系统…

06Docker-Compose和微服务部署

Docker-Compose 概述 Docker Compose通过一个单独的docker-compose.yml模板文件来定义一组相关联的应用容器&#xff0c;帮助我们实现多个相互关联的Docker容器的快速部署 一般一个docker-compose.yml对应完整的项目,项目中的服务和中间件对应不同的容器 Compose文件实质就…

锂电池寿命预测 | Matlab基于SSA-SVR麻雀优化支持向量回归的锂离子电池剩余寿命预测

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 【锂电池剩余寿命RUL预测案例】 锂电池寿命预测 | Matlab基于SSA-SVR麻雀优化支持向量回归的锂离子电池剩余寿命预测&#xff08;完整源码和数据&#xff09; 1、提取NASA数据集的电池容量&#xff0c;以历史容量作…

【C++课程学习】:类和对象(上)(类的基础详细讲解)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f35f;1.1类的引出&#xff1a; &#x1f35f;1.2类的结构&#xff1a; &#x1f35f;1.3类的…

LeetCode-82. 删除排序链表中的重复元素 II【链表 双指针】

LeetCode-82. 删除排序链表中的重复元素 II【链表 双指针】 题目描述&#xff1a;解题思路一&#xff1a;用一个cur即可实现去重cur.next cur.next.next背诵版&#xff1a;解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个已排序的链表的头 head &#xff0c; 删除原始…

十大排序-冒泡排序

算法原理如下&#xff1a; 给出一组数据&#xff1b;比较相邻的元素。如果第一个比第二个大&#xff0c;互换两个值。对每一组相邻元素同样方式比较&#xff0c;从开始的第一组到结束的最后一组。最后的元素会是最大数。除了排列好的最大数&#xff0c;针对所有元素重复以上步…

前端应用开发实验:组件应用

目录 实验目的相关知识点实验内容及要求代码实现效果 实验目的 &#xff08;1&#xff09;掌握组件的创建方法&#xff08;全局组件、局部组件&#xff09;&#xff1b; &#xff08;2&#xff09;重点学会组件之间的数据传递&#xff08;prop传值、自定义事件&#xff09;&am…

SAP 用事务码SQVI 制作简单的ALV报表

我们在项目实施和运维的过程中经常会接到用户的很多需求&#xff0c;有很大的一部分需求可能都是一些报表的需求&#xff0c;有些报表的需求需要开发人员使用ABAP编写&#xff0c;但是有些报表仅仅只是两个或者多个报表的表关联就可以实现。这个时候我们就可以用SQVI这个事物代…

揭秘!宠物空气净化器对抗猫毛过敏,效果真的超乎想象?

猫毛过敏困扰着不少爱猫人士。尽管网络上充斥着各种缓解策略&#xff0c;但究竟哪种方法效果最佳&#xff1f;作为一位经验丰富的宠物主人&#xff0c;我搜集了大量信息&#xff0c;对比了几种主流的猫毛过敏应对策略&#xff0c;比如药物治疗、日常清洁和宠物空气净化器的使用…

阿里云私有CA使用教程

点击免费生成 根CA详情 启用根CA -----BEGIN CERTIFICATE----- MIIDpzCCAogAwIBAgISBZ2QPcfDqvfI8fqoPkOq6AoMA0GCSqGSIb3DQEBCwUA MFwxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdiZWlqaW5nMRAwDgYDVQQHDAdiZWlq aW5nMQ0wCwYDVQQKDARDU0REMQ0wCwYDVQQLDARDU0REMQswCQYDVQQDDAJDTjA…

单列集合--ArryList、LinkedList、Set

使用IDEA进入某个类之后&#xff0c;按ctrlF12,或者alt数字7&#xff0c;可查看该实现类的大纲。 package exercise;import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.function.Consumer;public class Demo3 {public static void…

开放式耳机哪个牌子好?2024年度热门机型推荐榜单分享!

随着音乐技术的不断革新&#xff0c;开放式耳机已成为音乐发烧友们的首选。从最初的简单音质&#xff0c;到如今的高清解析&#xff0c;开放式耳机不断进化。音质纯净&#xff0c;佩戴舒适&#xff0c;无论是街头漫步还是家中细细静听&#xff0c;都能带给你身临其境的音乐体验…

iOS18 新变化提前了解,除了AI还有这些变化

iOS 18即将在不久的将来与广大iPhone用户见面&#xff0c;这次更新被普遍认为是苹果历史上最重要的软件更新之一。据多方报道和泄露的消息&#xff0c;iOS 18将带来一系列全新的功能和改进&#xff0c;包括在人工智能领域的重大突破、全新的设计元素以及增强的性能和安全性。现…

AI教我变得厉害的思维模式01 - 成长型思维模式

今天和AI一起思考如何培养自己的成长性思维。 一一核对&#xff0c;自己哪里里做到&#xff0c;哪里没有做到&#xff0c;让AI来微调训练我自己。 成长性思维的介绍 成长性思维&#xff08;Growth Mindset&#xff09;是由斯坦福大学心理学教授卡罗尔德韦克&#xff08;Carol…

钡铼技术BL103助力实现PLC到OPC-UA无缝转换新高度

在工业4.0的大背景下&#xff0c;信息物理系统和工业物联网的融合日益加深&#xff0c;推动了工业自动化向更高层次的发展。OPC UA作为一种开放、安全、跨平台的通信协议&#xff0c;在实现不同设备、系统间数据交换和互操作性方面扮演了核心角色。钡铼技术公司推出的BL103 PLC…