核心思想

  1. 集成 AbstractRoutingDataSource 并重写 determineCurrentLookupKey 方法。determineCurrentLookupKey 方法的返回值决定了spring使用那个数据源,如果返回null则使用默认数据源。
  2. 在AbstractRoutingDataSource 的构造函数中完成 多数据源和默认数据源的注册。
  3. 定义 DruidConfig配置,该配置文件主要将三个 数据源注册到spring中,并且将 主数据员和第二个数据源通过AbstractRoutingDataSource 的实现注册到spring中。

核心代码

  • pom

            <!-- Druid 数据连接池依赖 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version></dependency><!--MyBatis整合SpringBoot框架的起步依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- jdbc --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><version>2.7.8</version></dependency><!-- Mysql驱动包 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency>
    
  • yml配置

    # 数据源配置
    spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverdruid:# 主库数据源master:url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useUnicode=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8username: rootpassword: 123456# 从库数据源slave:# 从数据源开关/默认关闭enabled: trueurl: jdbc:mysql://43.143.217.124:3306/hongbei?characterEncoding=utf8&useUnicode=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8username: rootpassword: 123456# 初始连接数initialSize: 5# 最小连接池数量minIdle: 10# 最大连接池数量maxActive: 20# 配置获取连接等待超时的时间maxWait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒minEvictableIdleTimeMillis: 300000# 配置一个连接在池中最大生存的时间,单位是毫秒maxEvictableIdleTimeMillis: 900000# 配置检测连接是否有效validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsewebStatFilter:enabled: truestatViewServlet:enabled: true# 设置白名单,不填则允许所有访问allow:url-pattern: /druid/*# 控制台管理用户名和密码login-username: ruoyilogin-password: 123456filter:stat:enabled: true# 慢SQL记录log-slow-sql: trueslow-sql-millis: 1000merge-sql: truewall:config:multi-statement-allow: true
    
  • 前置工作1 定义数据库类型(属于规范)

    
    /*** @Classname DataSourceType* @Description DataSourceType* @Date 2023-02-27 15:44* @Created by lihw*/
    public enum DataSourceType {/*** 主库*/MASTER,/*** 从库*/SLAVE
    }
  • druid属性绑定,以及工具方法

    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;/*** @Classname DruidProperties* @Description DruidProperties* @Date 2023-02-27 15:28* @Created by lihw*/
    @Configuration
    public class DruidProperties {@Value("${spring.datasource.druid.initialSize}")private int initialSize;@Value("${spring.datasource.druid.minIdle}")private int minIdle;@Value("${spring.datasource.druid.maxActive}")private int maxActive;@Value("${spring.datasource.druid.maxWait}")private int maxWait;@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")private int timeBetweenEvictionRunsMillis;@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")private int minEvictableIdleTimeMillis;@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")private int maxEvictableIdleTimeMillis;@Value("${spring.datasource.druid.validationQuery}")private String validationQuery;@Value("${spring.datasource.druid.testWhileIdle}")private boolean testWhileIdle;@Value("${spring.datasource.druid.testOnBorrow}")private boolean testOnBorrow;@Value("${spring.datasource.druid.testOnReturn}")private boolean testOnReturn;public DruidDataSource dataSource(DruidDataSource datasource) {/** 配置初始化大小、最小、最大 */datasource.setInitialSize(initialSize);datasource.setMaxActive(maxActive);datasource.setMinIdle(minIdle);/** 配置获取连接等待超时的时间 */datasource.setMaxWait(maxWait);/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);/*** 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。*/datasource.setValidationQuery(validationQuery);/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */datasource.setTestWhileIdle(testWhileIdle);/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */datasource.setTestOnBorrow(testOnBorrow);/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */datasource.setTestOnReturn(testOnReturn);return datasource;}
    }
  • 定义动态数据源 以及 动态数据源 控制器

    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
    import java.util.Map;/*** @Classname DynamicDataSource* @Description DynamicDataSource* @Date 2023-02-27 15:39* @Created by lihw*/
    public class DynamicDataSource extends AbstractRoutingDataSource {/*** 数据源初始化操作,把多个数据源以及默认数据源注册到spring中* @param defaultTargetDataSource* @param targetDataSources*/public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources){super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}/*** 返回值决定使用哪个数据源,如果返回 null 则使用默认数据源* @return*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
    }
    
  • 数据源控制器

    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;/*** @Classname DynamicDataSourceContextHolder* @Description DynamicDataSourceContextHolder* @Date 2023-02-27 15:39* @Created by lihw*/
    public class DynamicDataSourceContextHolder {public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);/*** 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,*  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();/*** 设置数据源的变量*/public static void setDataSourceType(String dsType){log.info("切换到{}数据源", dsType);CONTEXT_HOLDER.set(dsType);}/*** 获得数据源的变量*/public static String getDataSourceType(){return CONTEXT_HOLDER.get();}/*** 清空数据源变量*/public static void clearDataSourceType(){CONTEXT_HOLDER.remove();}
    }
    
  • 把多个数据源注册到动态数据源中, 并标记动态数据源为主数据源

    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    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;/*** @Classname DruidConfig* @Description DruidConfig* @Date 2023-02-27 15:31* @Created by lihw*/
    @Configuration
    public class DruidConfig implements BeanFactoryPostProcessor {/** Spring应用上下文环境,用于管理 注册到 spring中所有的bean */private static ConfigurableListableBeanFactory beanFactory;/*** 注册主数据源* @param druidProperties* @return*/@Bean@ConfigurationProperties("spring.datasource.druid.master")public DataSource masterDataSource(DruidProperties druidProperties){DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);}/*** 根据条件 : 注册 第二个数据源* @param druidProperties* @return*/@Bean@ConfigurationProperties("spring.datasource.druid.slave")@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")public DataSource slaveDataSource(DruidProperties druidProperties) {DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);}/*** 把 注册的多个数据源, 统一加入到动态数据源中,并把动态数据源注册到spring中,以便动态切换* @param masterDataSource* @return*/@Bean("dynamicDataSource")// 标记动态数据源为主数据源,在相同的条件下,优先使用 此数据源@Primarypublic DynamicDataSource dataSource(DataSource masterDataSource){Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");return new DynamicDataSource(masterDataSource, targetDataSources);}/*** 设置数据源** @param targetDataSources 备选数据源集合* @param sourceName 数据源名称* @param beanName bean名称*/public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName){try{DataSource dataSource = (DataSource) beanFactory.getBean(beanName);targetDataSources.put(sourceName, dataSource);}catch (Exception e){}}/*** 重写BeanFactoryPostProcessor 的方法,用户接收 bean 工厂回调,bean 工厂中存了所有spring中注册的bean* @param configurableListableBeanFactory the bean factory used by the application context* @throws BeansException*/@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {beanFactory = configurableListableBeanFactory;}
    }
  • 测试代码

    @RequestMapping("/test")
    public class TestController {@AutowiredStudentMapper studentMapper;@AutowiredSysUserMapper sysUserMapper;@GetMapping("list")public List<Student> getUserList() {List<Student> userList = studentMapper.getUserList();System.out.println(userList);return userList;}@GetMapping("msg2")public String getmsg2() {// 手动切换 数据源DynamicDataSourceContextHolder.setDataSourceType("SLAVE");List<SysUser> user = sysUserMapper.getUserList();DynamicDataSourceContextHolder.clearDataSourceType();System.out.println(user);return "msg22";}}
    

测试结果

2023-02-27 16:25:57.770  INFO 6988 --- [nio-8080-exec-1] com.alibaba.druid.pool.DruidDataSource   :** {dataSource-1} inited
[Student(id=1612374211110744066, name=小红1, age=20, classId=1, teacherId=1, createTime=null, updateTime=null), Student(id=1612374211354013699, name=小红, age=20, classId=12, teacherId=21, createTime=null, updateTime=null), Student(id=1625387649604820994, name=小王, age=30, classId=null, teacherId=null, createTime=null, updateTime=null), Student(id=1625387978916392961, name=小王, age=30, classId=null, teacherId=null, createTime=null, updateTime=null), Student(id=1625389122829021186, name=小王, age=30, classId=null, teacherId=null, createTime=null, updateTime=null), Student(id=1625389706172817410, name=小王, age=30, classId=null, teacherId=null, createTime=null, updateTime=null), Student(id=1625389938096738305, name=小王, age=30, classId=null, teacherId=null, createTime=Tue Feb 14 15:02:14 CST 2023, updateTime=null)]**
2023-02-27 16:25:59.710  INFO 6988 --- [nio-8080-exec-3] c.e.d.d.DynamicDataSourceContextHolder   : 切换到SLAVE数据源
2023-02-27 16:26:00.757  INFO 6988 --- [nio-8080-exec-3] com.alibaba.druid.pool.DruidDataSource   : **{dataSource-2} inited
[SysUser(userId=1, userName=admin, nickName=若依), SysUser(userId=2, userName=ry, nickName=若依), SysUser(userId=100, userName=lhw, nickName=李洪伟)]**

利用spring 注解优雅切换

  • spring aop拦截器

            <!-- SpringBoot 拦截器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.7.9</version></dependency>
    
  • 定义注解

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import com.ruoyi.common.enums.DataSourceType;/*** 自定义多数据源切换注解** 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准** @author ruoyi*/
    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface DataSource
    {/*** 切换数据源名称*/public DataSourceType value() default DataSourceType.MASTER;
    }
  • aop 切面

    import java.util.Objects;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.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;/*** 多数据源处理** @author ruoyi*/
    @Aspect
    @Order(1)
    @Component
    public class DataSourceAspect
    {protected Logger logger = LoggerFactory.getLogger(getClass());@Pointcut("@annotation(com.example.demo.druid2.DataSource)"+ "|| @within(com.example.demo.druid2.DataSource)")public void dsPointCut(){}@Around("dsPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable{DataSource dataSource = getDataSource(point);if (dataSource != null){DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());}try{return point.proceed();}finally{// 销毁数据源 在执行方法之后DynamicDataSourceContextHolder.clearDataSourceType();}}/*** 获取需要切换的数据源*/public DataSource getDataSource(ProceedingJoinPoint point){MethodSignature signature = (MethodSignature) point.getSignature();DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);if (Objects.nonNull(dataSource)){return dataSource;}return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);}
    }

多数据源动态优雅切换相关推荐

  1. Proxool配置多数据源动态切换

    2019独角兽企业重金招聘Python工程师标准>>> 前段时间遇到多数据源动态切换问题,总结一下,做个记录,以备后续之需! 首先附上proxool连接池的配置方法:http://3 ...

  2. springboot多数据源动态切换和自定义mybatis分页插件

    1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...

  3. SpringBoot+AOP实现多数据源动态切换

    SpringBoot+AOP实现多数据源动态切换 背景 设计总体思路 步骤 背景 系统后端需要访问多个数据库,现有的数据库连接配置写入配置文件中.后端需要从一个数据库的配置表里动态的读取其它mysql ...

  4. Spring+Mybatis多数据源配置(四)——AbstractRoutingDataSource实现数据源动态切换

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  5. Spring-Boot + AOP实现多数据源动态切换

    2019独角兽企业重金招聘Python工程师标准>>> 最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据 ...

  6. springboot使用mybatis多数据源动态切换的实现

    需求:项目使用了读写分离,或者数据进行了分库处理,我们希望在操作不同的数据库的时候,我们的程序能够动态的切换到相应的数据库,执行相关的操作. 首先,你需要一个能够正常运行的springboot项目,配 ...

  7. 多数据源解决方案——AOP实现多数据源动态切换

    文章目录 1. 场景描述 2. 项目依赖 3. 场景实现 4. 项目源代码 GitHub 1. 场景描述 在springboot开发中,可能遇见操作多个数据库的情形.一种解决方案就是:配置动态数据源, ...

  8. 基于spring多数据源动态调用及其事务处理

    需求: 有些时候,我们需要连接多个数据库,但是,在方法调用前并不知道到底是调用哪个.即同时保持多个数据库的连接,在方法中根据传入的参数来确定. 下图的单数据源的调用和多数据源动态调用的流程,可以看出在 ...

  9. Spring3 整合MyBatis3 配置多数据源 动态选择SqlSessionFactory

    一.摘要 上两篇文章分别介绍了Spring3.3 整合 Hibernate3.MyBatis3.2 配置多数据源/动态切换数据源 方法 和 Spring3 整合Hibernate3.5 动态切换Ses ...

最新文章

  1. 创始人CEO该拿多少工资?
  2. 【Linux系统编程】Linux 可执行文件结构与进程结构
  3. 统计 Github 2021 贡献过的开源项目
  4. [react] 在React中怎么使用async/await?
  5. [设计模式] ------ 适配器模式
  6. hadoop实训报告总结及体会_管理工程学院举办电子商务实训表彰大会
  7. Phaser3让超级玛丽实现轻跳、高跳及加上对应的跳跃声音
  8. windows2019迁移FSMO
  9. 应用高斯分布来解决异常检测问题(一)
  10. Android Studio生成函数注释
  11. Spring+Netty4实现的简单通信框架
  12. arduino+16路舵机驱动板连接测试
  13. 项目日报模板_接手一个新项目应该如何入手
  14. pip 安装小白教程
  15. oracle remap语句格式,Oracle remap_schema需要注意的问题
  16. git prune 相关
  17. 无线MESH自组网系统
  18. 丰巢快递柜收费,究竟挑动了我们哪根神经?
  19. 均州古城(走遍中国08.10.27)
  20. 一个披着 Windows 外壳的轻量级 Linux 系统

热门文章

  1. 第一代计算机磁鼓,磁鼓计算机
  2. 用Python爬取需要登录的网站
  3. php实习生实习日记,实习日记
  4. [转载]投资正道vs冯时能30年股票投资16心得
  5. 通过训练获得最优量化区间——Quantization Interval Learning
  6. 【数据结构与算法】一篇文章彻底搞懂二分查找(思路图解+代码优化)两种实现方式,递归与非递归
  7. 【C++ Primer Plus(6)】CH2 开始学习C++
  8. hbase写入一段时间后变的越来越慢
  9. 从浏览器地址栏输入url到请求返回发生了什么?
  10. 全媒体运营师胡耀文教你:产品运营如何进行产品规划?