SpringBoot+Mybatis-plus实现多数据源动态切换的两种方式

2023-10-27

一、自定义注解方式

本文中使用的数据源为HikariCP,实现数据源之间的切换用@DataSource自定义注解,配置AOP进行切换。需要引用的包此处不再说明,yml中mysql相关配置如下:

datasource:
    # 动态数据源配置
    dynamic:
      hikari:
        # 池中维护的最小空闲连接数
        min-idle: 5
        # 池中最大连接数,包括闲置和使用中的连接
        max-pool-size: 20
        # 池中连接最长生命周期
        max-lifetime: 1800000
        # 自动提交从池中返回的连接
        is-auto-commit: true
        # 连接允许在池中闲置的最长时间
        idle-timeout: 30000
        # 等待来自池的连接的最大毫秒数
        connection-timeout: 30000
        # 连接池名称
        pool-name: HikariCP_1
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      # 开启seata
      seata: false
      datasource:
        # 主数据库
        master:
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://${MYQSL_HOST:localhost}:${MYSQL_PORT:3306}/table?useUnicode=true&useSSL=false&characterEncoding=utf8&allowMultiQueries=true&rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
          type: com.zaxxer.hikari.HikariDataSource
        # 用户数据库
        slave:
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://${MYQSL_HOST:localhost}:${MYSQL_PORT:3306}/table_1?useUnicode=true&useSSL=false&characterEncoding=utf8&allowMultiQueries=true&rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
          type: com.zaxxer.hikari.HikariDataSource

1、先创建一个动态数据源切换类

package com.flying.demo.config;

import lombok.NoArgsConstructor;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

/**
 * @Description 动态数据源切换类
 */
@NoArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> DB_CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 取得当前使用的数据源
     * @return 当前使用的数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    /**
     * 设置数据源
     * @param dataSource 数据源
     */
    public static void setDataSource(String dataSource) {
        DB_CONTEXT_HOLDER.set(dataSource);
    }

    /**
     * 获取当前数据源
     * @return 数据源
     */
    public static String getDataSource() {
        return DB_CONTEXT_HOLDER.get();
    }

    /**
     * 清除上下文
     */
    public static void clearDataSource() {
        DB_CONTEXT_HOLDER.remove();
    }

    /**
     * 设置默认数据源,和可切换的数据源Map
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources 可切换的数据源Map
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
}

2、多数据源配置类

package com.flying.demo.config;

import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description 多数据源配置类。
 */
@Component
@Configuration
public class DataSourceConfig {

    /**
     * 创建数据源master
     * @return 数据源master
     */
    @Bean(name = "master")
    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建数据源slave
     * @return 数据源slave
     */
    @Bean(name = "slave")
    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 数据源配置
     * @param master 数据源master
     * @param slave 数据源slave
     * @return 动态数据源切换对象。
     * @Description @Primary赋予该类型bean更高的优先级,使至少有一个bean有资格作为autowire的候选者。
     */
    @Bean
    @Primary
    public DataSource dataSource(@Qualifier("master") DataSource master,
                                 @Qualifier("slave") DataSource slave) {
        Map<Object, Object> dsMap = new HashMap<>(2);
        dsMap.put("master", master);
        dsMap.put("slave", slave);
        return new DynamicDataSource(master, dsMap);
    }
}

需要在启动类上引入此配置,并且排除springboot数据源的自动配置,代码如下:

/**
 * @Description 启动入口
 */
//@SpringBootApplication
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@Import({DataSourceConfig.class})
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

3、创建自定义注解,用来指定使用哪个数据源

package com.flying.demo.common.annotation;

import java.lang.annotation.*;

/**
 * @Description 在方法上使用,用于指定使用哪个数据源
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}

4、创建AOP,根据注解的参数切换数据源

package com.flying.demo.common.aop;

import com.flying.demo.common.annotation.DataSource;
import com.flying.demo.config.DynamicDataSource;
import lombok.extern.slf4j.Slf4j;
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.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @Description 多数据源切换AOP,@Order(-100)是为了保证AOP在事务注解之前生效,Order的值越小,优先级越高
 */
@Aspect
@Component
@Order(-100)
@Slf4j
public class DataSourceAspect {

    @Pointcut("execution(* com.flying.demo.service..*.*(..))")
    private void dsPointCut() {
    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取当前指定的数据源
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);

        if (Objects.isNull(dataSource)) {
            // 使用默认数据源
            DynamicDataSource.setDataSource("master");
            log.info("未匹配到数据源,使用默认数据源");
        } else {
            // 匹配到的话,设置到动态数据源上下文中
            DynamicDataSource.setDataSource(dataSource.name());
            log.info("匹配到数据源:{}", dataSource.name());
        }

        try {
            // 执行目标方法,返回值即为当前方法的返回值
            return joinPoint.proceed();
        } finally {
            // 方法执行完毕之后,销毁当前数据源信息,进行垃圾回收
            DynamicDataSource.clearDataSource();
            log.info("当前数据源已清空");
        }
    }
}

5、测试

新建数据库 table 和 table_1,两个数据库中分别创建教师表teacher如下:

CREATE TABLE `teacher` (
  `guid` bigint(20) NOT NULL COMMENT '主键ID',
  `cn_name` varchar(32) DEFAULT NULL COMMENT '中文名',
  `en_name` varchar(32) DEFAULT NULL COMMENT '英文名',
  `gender` varchar(8) DEFAULT NULL COMMENT '性别',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

两个数据库中的teacher表中各新增一条数据如图:
table.teacher
table.teacher
table_1.teacher
table_1.teacher
写两个查询方法,getOneById()查询主数据库master(即table),getAll()查询从数据库slave(即table_1)

@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements TeacherService {

    @Override
    @DataSource(name = "master")
    public Teacher getOneById(Long teacherId) {
        return this.getById(teacherId);
    }

    @Override
    @DataSource(name = "slave")
    public List<Teacher> getAll() {
        return this.list();
    }
}
@Slf4j
@RestController
@RequestMapping("/teacher")
public class TeacherController {

    @Autowired
    TeacherService teacherService;

    @ApiOperation(value = "查询")
    @GetMapping("/query")
    public void query(@RequestParam("teacherId") Long teacherId) {
        Teacher teacher = teacherService.getOneById(teacherId);
        System.out.println("DB:ffsong Teacher.name = " + teacher.getCnName());

        List<Teacher> teachers = teacherService.getAll();
        System.out.println("DB:ffsong_1 Teacher.name = " + teachers.get(0).getCnName());
    }
}

同时调用以上两个方法,结果如下:
在这里插入图片描述

二、使用 @DS切换数据源

mybatis-plus 官方文档中提出了另一种更加便捷的方案,使用 @DS切换数据源。详细可到官网中查看。
https://baomidou.com/pages/a61e1b/#%E6%96%87%E6%A1%A3-documentation

1、引入依赖

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

2、配置数据源

在文章最开始的部分已经配置过了。

3、通过@DS切换数据源

@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
在这里插入图片描述

4、测试

新建数据库 table 和 table_1,两个数据库中分别创建教师表student如下:

CREATE TABLE `student` (
  `guid` bigint(20) NOT NULL COMMENT '主键ID',
  `cn_name` varchar(32) DEFAULT NULL COMMENT '中文名',
  `en_name` varchar(32) DEFAULT NULL COMMENT '英文名',
  `gender` varchar(8) DEFAULT NULL COMMENT '性别',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

两个数据库中的student表中各新增一条数据如图:
table.student
在这里插入图片描述
table_1.student
在这里插入图片描述
写两个查询方法,分别查询两个库里的数据

@Service
@DS("master")
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    @Override
    public Student getOneById(Long studentId) {
        return this.getById(studentId);
    }

    @Override
    @DS("slave")
    public List<Student> getAll() {
        return this.list();
    }
}
@Slf4j
@RestController
@RequestMapping("/student")
public class StudentController {

    @Autowired
    StudentService studentService;

    @ApiOperation(value = "查询", tags = "多数据源")
    @GetMapping("/query")
    public void query(@RequestParam("studentId") Long studentId, @RequestParam("teacherId") Long teacherId) {

        Student student = studentService.getOneById(studentId);
        System.out.println("DB:table Student.name = " + student.getCnName());

        List<Student> students = studentService.getAll();
        System.out.println("DB:table_1 Student.name = " + students.get(0).getCnName());
    }
}

同时调用以上两个方法,结果如下:
在这里插入图片描述
注意
1、使用 @DS 注解切换数据源时,使用springboot数据源的自动配置,需要将DataSourceConfig配置注释掉
2、使用 @DataSource 自定义注解时,排除springboot数据源的自动配置,引入DataSourceConfig配置
3、使用 @DS 时,数据源配置为:spring.datasource.dynamic.datasource.master.url
4、使用 @DataSource 时,数据源配置为:spring.datasource.dynamic.datasource.master.jdbc-url

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot+Mybatis-plus实现多数据源动态切换的两种方式 的相关文章

  • Mockito 在调用参数数量可变的方法时使用参数匹配器

    我试图在对具有可变数量参数的方法的调用中使用参数匹配器 Java 中的东西 没有成功 我的代码如下 我还将列出我尝试用来完成此工作的所有行 import static org mockito Mockito public class Met
  • 如何在 Spring Data 中选择不同的结果

    我在使用简单的 Spring Data 查询或 Query 或 QueryDSL 在 Spring Data 中构建查询时遇到问题 如何选择三列 研究 国家 登录 不同的行 并且查询结果将是用户对象类型的列表 Table User Id S
  • JPA 中的复合键

    我想创建一个具有自动生成的主键的实体 而且还有一个由其他两个字段组成的唯一复合键 我如何在 JPA 中执行此操作 我想这样做是因为主键应该用作另一个表中的外键 并且使其复合并不好 在下面的代码片段中 我需要命令和模型是唯一的 pk当然是主键
  • Java Runtime.getRuntime().freeMemory() 问题

    我搜索并看到了一些线程 但没有一个能够解决我遇到的具体问题 我正在尝试使用以下方式监视我的内存使用情况Runtime getRuntime freeMemory Runtime getRuntime maxMemory and Runtim
  • @RestController 没有 @ResponseBody 方法工作不正确

    我有以下控制器 RestController RequestMapping value base url public class MyController RequestMapping value child url method Req
  • 不同类型的数组

    是否可以有一个包含两种不同类型数据的数组 我想要一个包含双精度型和字符串的数组 我尝试过 ArrayList
  • Spring Boot自动装配存储库始终为空[重复]

    这个问题在这里已经有答案了 每次我进入我的服务类时 存储库似乎都没有自动连接 因为它不断抛出 NullPointerException 谁能帮我检查一下我缺少什么吗 这是我的代码 演示应用程序 java package com exampl
  • org.hibernate.QueryException:无法解析属性:文件名

    我正在使用休眠Criteria从列中获取值filename在我的桌子上contaque recording log 但是当我得到结果时 它抛出异常 org hibernate QueryException 无法解析属性 文件名 com co
  • ConcurrentHashMap 内部是如何工作的?

    我正在阅读有关 Java 并发性的 Oracle 官方文档 我想知道Collection由返回 public static
  • 需要使用 joda 进行灵活的日期时间转换

    我想使用 joda 解析电子邮件中的日期时间字符串 不幸的是我得到了各种不同的格式 例如 Wed 19 Jan 2011 12 52 31 0600 Wed 19 Jan 2011 10 15 34 0800 PST Wed 19 Jan
  • 如何使用 Hibernate (EntityManager) 或 JPA 调用 Oracle 函数或过程

    我有一个返回 sys refcursor 的 Oracle 函数 当我使用 Hibernate 调用该函数时 出现以下异常 Hibernate call my function org hibernate exception Generic
  • 在另一个模块中使用自定义 gradle 插件模块

    我正在开发一个自定义插件 我希望能够在稍后阶段将其部署到存储库 因此我为其创建了一个独立的模块 在对其进行任何正式的 TDD 之前 我想手动进行某些探索性测试 因此 我创建了一个使用给定插件的演示模块 到目前为止 我发现执行此操作的唯一方法
  • Freemarker 和 Struts 2,有时它计算为序列+扩展哈希

    首先我要说的是 使用 Struts2 Freemarker 真是太棒了 然而有些事情让我发疯 因为我不明白为什么会发生这种情况 我在这里问是因为也许其他人有一个想法可以分享 我有一个动作 有一个属性 说 private String myT
  • 流中的非终结符 forEach() ?

    有时 在处理 Java Stream 时 我发现自己需要一个非终端 forEach 来触发副作用但不终止处理 我怀疑我可以用 map item gt f item 之类的方法来做到这一点 其中方法 f 执行副作用并将项目返回到流中 但这似乎
  • QuerySyntaxException:无法找到类

    我正在使用 hql 生成 JunctionManagementListDto 类的实际 Java 对象 但我最终在控制台上出现以下异常 org hibernate hql internal ast QuerySyntaxException
  • java库维护数据库结构

    我的应用程序一直在开发 所以偶尔 当版本升级时 需要创建 更改 删除一些表 修改一些数据等 通常需要执行一些sql代码 是否有一个 Java 库可用于使我的数据库结构保持最新 通过分析类似 db structure version 信息并执
  • 是否可以使用 Java Guava 将函数应用于集合?

    我想使用 Guava 将函数应用于集合 地图等 基本上 我需要调整 a 的行和列的大小Table分别使所有行和列的大小相同 执行如下操作 Table
  • Java Swing:需要一个高质量的带有复选框的开发 JTree

    我一直在寻找一个 Tree 实现 其中包含复选框 其中 当您选择一个节点时 树中的所有后继节点都会被自动选择 当您取消选择一个节点时 树中其所有后继节点都会自动取消选择 当已经选择了父节点 并且从其后继之一中删除了选择时 节点颜色将发生变化
  • 泛型、数组和 ClassCastException

    我想这里一定发生了一些我不知道的微妙事情 考虑以下 public class Foo
  • 配置“DataSource”以使用 SSL/TLS 加密连接到 Digital Ocean 上的托管 Postgres 服务器

    我正在尝试托管数据库服务 https www digitalocean com products managed databases on 数字海洋网 https en wikipedia org wiki DigitalOcean 创建了

随机推荐

  • Xcode 调试之 Hello World

    编译 编译JKD 可以查看 Mac 编译 OpenJDK 8 调试 截图来自 https segmentfault com a 1190000005082098 调试 Hello World 编写好源文件 public class Hell
  • Linux命令行另类使用技巧

    0x00 基础简述 0x01 补全实践 bash completion zsh autosuggestions 文件名补全 特殊补全 命令行参数补齐 可编程补全 0x02 历史命令 设置历史记录 查看历史命令 搜索历史命令 前后移动历史命令
  • CSS层叠性(重要)

    CSS层叠性 重要 指多种CSS样式的叠加 是浏览器处理冲突的一个能力 如果一个属性通过两个相同选择器设置到同一个元素上 那么这个时候一个属性就会将另一个属性层叠掉 原则 1 样式冲突 遵循的原则是就近原则 即CSS的书写位置 2 样式不冲
  • 【2022年MathorCup大数据竞赛】B题:北京移动用户体验影响因素研究(一)

    目录 一 题目背景 二 初赛问题 三 数据集的分享 一 题目背景 移动通信技术飞速发展 给人们带来了极大便利 人们也越来越离不开移动通信技术带来的各种便捷 随着网络不断的建设 网络覆盖越来越完善 各个移动运营商 越来越重视客户的网络使用体验
  • 判断文件的几种方法及其优劣对比

    目录 一 懒人的try语句 二 传统的os模块 三 时尚的pathlib模块 四 几种方法优劣对比 我们知道当 件不存在的时候 open 法的写模式与追加模式都会新建 件 但是对 件进 判 我们之前学过 要 with语句来处理 件读写 但w
  • shell脚本生成两个数据日期之间的所有日期

    在linux下有时候会需要得到两个日期之间的所有日期 作为变量进行处理 例如两个日期之间的所有日期都分别生成一个用日期命名的文件夹 以下shell脚本循环输出两个指定日期之间的所有日期 包括两个输入日期 bin bash 以YYYYMMDD
  • 排列数【第十届】【决赛】【B组】

    在一个排列中 一个折点是指排列中的一个元素 它同时小于两边的元素 或者同时大于两边的元素 对于一个 1 n 的排列 如果可以将这个排列中包含 t个折点 则它称为一个 t 1 单调序列 例如 排列 1 4 2 3 是一个 3 单调序列 其中
  • GTK+的优点与QT的优点

    想看看图形界面系统的优缺点 转载了网友整理的 GTK 的优点与QT的优点整理 在嵌入式 Linux 下有很多图形界面系统 GUI 包括 Qt Embedded FLTK Microwindows 和 GTK 等 作为一个开发者 到底使用什么
  • YOLOv8+ByteTrack多目标跟踪(行人车辆计数与越界识别)

    课程链接 https edu csdn net course detail 38901 ByteTrack是发表于2022年的ECCV国际会议的先进的多目标跟踪算法 YOLOv8代码中已集成了ByteTrack 本课程使用YOLOv8和By
  • arcgis已试图对空几何执行该操作_ArcGIS中坐标转换和投影变换

    当不同来源 不同坐标系统的空间数据要在一起使用 相互参照时 就要进行坐标转换 如果涉及不同的地图投影 就要进行投影变换 动态投影 所谓动态投影 是指改变ArcMap中的数据框架 DataFrame 的空间参考或者对后加入ArcMap中的数据
  • liunx常用命令

    在liunx中可能有几百个命令 最常用的就10多个 liunx中最常用的命令 ls 功能 使用说明 案例 pwd 功能 使用说明 案例 cd 功能 使用说明 案例 mkdir 功能 使用说明 案例 touch 功能 使用说明 案例 rm 功
  • 动力节点 SpringBoot教程 p15 Whitelabel Error Page

    一步一步跟着王妈妈敲的 还是报错 后来发现是目录的问题 initiizer初始化出来application和comtroller不在一个包下面 把他挪到controller包下面就ok了 或者把他挪到和controller目录一个层次也可以
  • Android--沉浸式导航栏适配

    转自 Android 沉浸式导航栏适配 Aruba233的博客 CSDN博客 本文是用于设配SDK4 4到5 0的沉浸式导航栏适配 4 4下面的实现不了沉浸式 上次说到适配沉浸式状态栏时 为DecorView添加一个View可以是实现 导航
  • 包装类的使用

    包装类的使用 1 相关概念 java提供了8种基本数据类型对应的包装类 使得基本数据类型的变量具有类的特征 需要掌握 基本数据类型 包装类 String三者之间的相互转换 2 基本数据类型和包装类的互转 基本数据类型转换包装类 调用包装类的
  • EMI 滤 波 器 原 理 与 设 计 方 法 详 解

    输入端差模电感的选择 差模 choke 置于 L 线或 N 线上 同时与 XCAP 共同作用 F 1 2 L C 波器振荡频率要低于电源供给器的工作频率 一般要低于 10kHz L N2AL nH N2 nH N L nH AL nH N2
  • qt 动画(界面跳转进场动画)

    目标 做上位机软软件的时候 觉得QTabWidget的每个tab跳转时候 单纯的界面显示太过单调 希望有界面上面的控件有一个进场的动画效果 效果 实现 通过动画QPropertyAnimation把设置单个控件动画效果 在用组合动画类QSe
  • [编程入门]二维数组的转置

    题目描述 写一个函数 使给定的一个二维数组 转置 即行列互换 输入 一个3x3的矩阵 输出 无 样例输入复制 1 2 3 4 5 6 7 8 9 样例输出复制 1 4 7 2 5 8 3 6 9 思路 二维数组转置 即为i变为j j变为i
  • rabbitmq 常用配置

    rabbitmq 常用配置 rabbitmq 指定RabbitMQ host 默认为 localhost spring rabbitmq host rabbitmq piecloud infra xx 端口号 默认端口号5672 sprin
  • 云计算与Kubernetes(k8s)

    参考链接 https blog csdn net zkkzpp258 article details 86541362 https blog csdn net Bubbler 726 article details 85596418 htt
  • SpringBoot+Mybatis-plus实现多数据源动态切换的两种方式

    一 自定义注解方式 本文中使用的数据源为HikariCP 实现数据源之间的切换用 DataSource自定义注解 配置AOP进行切换 需要引用的包此处不再说明 yml中mysql相关配置如下 datasource 动态数据源配置 dynam