springboot+dynamic-datasource实现多数据源动态切换,非@DS注解方式

news2024/11/24 16:44:01

springboot+dynamic-datasource实现多数据源动态切换,非注解

    • 一、前言
    • 二、方案思路
    • 三、代码实现

一、前言

最近在分析SaaS平台多租户的功能,必然涉及数据库部分的功能,多租户的设计方案要考虑租户隔离数据和租户共享数据,共享数据好实现,但是隔离数据相对复杂一些,一般要考虑隔离性扩展性租户成本运维复杂性
通常SaaS多租户在数据存储上存在三种主要的方案:

  1. 独立数据库:一个租户一个数据库。
  2. 共享数据库,隔离数据架构:多个或所有租户共享database,但不同的tenantschema
  3. 共享数据库,共享数据架构:租户共享一个database、一个schema,在表中通过tenantID区分租户的数据。

注:由于平时工作中会涉及到多数据的功能,所以今天我们分享第一种方案,不只是适用于多租户的应用场景;

二、方案思路

在这里插入图片描述
详情:
1)租户数据库连接信息是从租户管理数据库中查询到的;
2)根据租户配置动态的新增、删除对应租户数据库信息;
3)根据租户请求头中的clientId字段判断使用的数据库连接,非@DS注解方式

三、代码实现

pom.xml

<!--mybatis-plus的springboot支持-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>

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

注:使用dynamic-datasource实现多数据源功能,在dynamic-datasource的使用样例中更多的是使用@DS注解实现,今天样例分享使用aop动态切换数据源

Controller.java

@RestController
@AllArgsConstructor
public class TestController {

    private final UserService userService;

    @GetMapping("/test/list")
    public List<User> getUserList(){
        return userService.queryAll();
    }


}

测试功能接口,bean和dao不再示例

application.yml

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志
    map-underscore-to-camel-case: true
    # 该配置就是将带有下划线的表字段映射为驼峰格式的实体类属性
spring:
  # 配置时区
  jackson:
    time-zone: GMT+8
  # 数据源相关配置
  datasource:
    dynamic:
      # 主数据源
      primary: db1
      datasource:
        # 数据源1
        db1:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
          username: root
          password: admin_123
      # druid 全局配置
      druid:
        initial-size: 5
        min-idle: 5
        max-active: 20
        max-wait: 60000
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 30000
        validation-query: SELECT 1 FROM DUAL
        test-while-idle: true
        test-on-borrow: true
        test-on-return: false
server:
  port: 8089

SourceConstants.java

package com.sk.common;

public class SourceConstants {

    /** 数据源查询sql */
    public static final String SELECT_SOURCE = "select s.slave, s.username, s.password, s.url, s.driver_class_name from te_source s where s.status = 0 and s.del_flag = 0";

    /** 数据源字段 */
    public enum Details {
        SLAVE("slave", "数据源编码"),
        USERNAME("username", "用户名"),
        PASSWORD("password", "密码"),
        URL_PREPEND("url_prepend", "连接地址"),
        URL("url", "连接地址"),
        URL_APPEND("url_append", "连接参数"),
        DRIVER_CLASS_NAME("driver_class_name", "驱动");
        private final String code;
        private final String info;
        Details(String code, String info) {
            this.code = code;
            this.info = info;
        }
        public String getCode() {
            return code;
        }
        public String getInfo() {
            return info;
        }
    }
}

常量配置

SourceProperties.java

@Data
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.db1")
public class SourceProperties {

    /** 数据源驱动 */
    private String driverClassName;

    /** 数据源路径 */
    private String url;

    /** 数据源账号 */
    private String username;

    /** 数据源密码 */
    private String password;
    
}

数据源信息表

DROP TABLE IF EXISTS `te_source`;
CREATE TABLE `te_source`  (
  `slave` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `driver_class_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` int(255) NULL DEFAULT NULL,
  `del_flag` int(255) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of te_source
-- ----------------------------
INSERT INTO `te_source` VALUES ('db2', 'root', 'admin_123', 'jdbc:mysql://localhost:3306/test02?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8', 'com.mysql.cj.jdbc.Driver', 0, 0);
INSERT INTO `te_source` VALUES ('db3', 'root', 'admin_123', 'jdbc:mysql://localhost:3306/test03?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8', 'com.mysql.cj.jdbc.Driver', 0, 0);

DynamicDataSourceLoading.java

/**
 * 子数据源加载
 *
 * @author xueyi
 */
@Configuration
@AllArgsConstructor
public class DynamicDataSourceLoading {

    private final SourceProperties sourceProperties;

    @Bean
    public DynamicDataSourceProvider jdbcDynamicDataSourceProvider() {
        return new AbstractJdbcDataSourceProvider(sourceProperties.getDriverClassName(), sourceProperties.getUrl(), sourceProperties.getUsername(), sourceProperties.getPassword()) {
            @Override
            protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
                ResultSet rs = statement.executeQuery(SourceConstants.SELECT_SOURCE);
                Map<String, DataSourceProperty> map = new HashMap<>();
                while (rs.next()) {
                    String name = rs.getString(Details.SLAVE.getCode());
                    String username = rs.getString(Details.USERNAME.getCode());
                    String password = rs.getString(Details.PASSWORD.getCode());
                    //String url = rs.getString(Details.URL_PREPEND.getCode()).concat(rs.getString(Details.URL_APPEND.getCode()));
                    String url = rs.getString(Details.URL.getCode());
                    String driver = rs.getString(Details.DRIVER_CLASS_NAME.getCode());
                    DataSourceProperty property = new DataSourceProperty();
                    property.setUsername(username);
                    property.setPassword(password);
                    property.setUrl(url);
                    property.setDriverClassName(driver);
                    map.put(name, property);
                }
                return map;
            }
        };
    }
}

查询所有数据源信息,加载各数据库连接

TransformDataSource.java

@Log4j2
@Aspect // FOR AOP
@Configuration // 配置类
public class TransformDataSource {

    @Pointcut("execution( * com.sk.controller..*.*(..))")
    /**
     * 这个方法的方法名要和下面注解方法名一致
     */
    public void doPointcut() {
    }

    @Before("doPointcut()")
    public void doBefore(JoinPoint joinPoint) {
        // 请求开始时间
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String headValue = sra.getRequest().getHeader("clientId");
        log.info("--------------clientId:{}", headValue);
        DynamicDataSourceContextHolder.poll();
        DynamicDataSourceContextHolder.push(headValue);
    }

    @After("doPointcut()")
    public void doAfter() {
        System.out.println("==doAfter==");
    }

}

使用aop根据请求头中的clientId数据使用不同的数据库连接

请求1
在这里插入图片描述
请求2
在这里插入图片描述

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

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

相关文章

concrt140.dll怎么修复?哪种修复方法更值得推荐

运行软件的时候&#xff0c;计算提示找不到concrt140.dll&#xff0c;第一次遇到的小伙伴肯定不知道是什么意思。concrt140.dll是Microsoft Visual C Redistributable for Visual Studio 2015的一部分。它是一个动态链接库文件&#xff0c;包含在Windows操作系统中。这个文件主…

【Python】瓶装液位检测系统

文章目录 概要效果图整体架构流程技术细节 概要 本代码是一个简单的GUI应用程序&#xff0c;用于瓶装液位检测系统。 效果图 整体架构流程 整体架构流程如下&#xff1a; 创建GUI窗口和必要的部件&#xff1a; 创建一个主窗口&#xff08;root&#xff09;作为GUI应用程序的…

分享一套开源充电桩云平台(v2.5.1)-- 支持二轮(电动自行车)、四轮(电动汽车)

开源充电桩云平台&#xff08;v2.5.1&#xff09; 支持二轮(电动自行车)、四轮(电动汽车) 后台体验地址 二轮后台体验地址&#xff0c;star star &#xff1a; 点我访问 四轮后台体验地址&#xff0c;star star &#xff1a; 点我访问 用户端二维码 公众号二维码 小程序二维…

攻防世界-WEB2

代码审计 首先进行代码审计 <?php $miwen"a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";function encode($str){$_ostrrev($str);// echo $_o;for($_00;$_0<strlen($_o);$_0){$_csubstr($_o,$_0,1);$__ord($_c)1;$_cchr($__);$_$_.$_c; …

2023- itwangyang - mac mysql 终端启动命令

在mac上使用mysql终端进行操作时&#xff0c;需要先启动mysql服务。以下是启动mysql服务的命令&#xff1a; sudo /usr/local/mysql/support-files/mysql.server start执行该命令后&#xff0c;会出现一些提示信息&#xff0c;等待一段时间后mysql服务就启动成功了。 接下来&…

【Java】面向对象基础 之 继承

一、继承 在前面的章节中&#xff0c;我们已经定义了Person类&#xff1a; class Person {private String name;private int age;public String getName() {...}public void setName(String name) {...}public int getAge() {...}public void setAge(int age) {...} }现在&am…

git报错:remote: Access denied (URL 403)

git报错&#xff1a;remote: Access denied fatal: unable to access ‘ https:/ /gitee. cohe requested URL 403 大概的原因&#xff0c;是之前更改了 可能因为我之前在git bash中配过ssh&#xff0c;系统已经将指向git的用户设置了别的位置&#xff0c;所以…

DAY44:动态规划(四)整数拆分(递归+DP递推都可以做,注意区别和理解)

文章目录 343.整数拆分思路1&#xff1a;递归法&#xff08;最直观的想法&#xff09;递归思路普通递归写法注意点&#xff1a;max的嵌套普通递归存在的问题 记忆化搜索递归写法时间复杂度 递归解法总结 思路2&#xff1a;动态规划&#xff08;注意递推的理解&#xff09;确认D…

Transformer 模型详解

Transformer模型 https://blog.csdn.net/m0_67084346/article/details/128138486 https://blog.csdn.net/benzhujie1245com/article/details/117173090 2017 年&#xff0c;Google 在论文 Attention is All you need 中提出了 Transformer 模型&#xff0c;其使用 Self-Atten…

一个SpringMVC的小项目

一个图书管理小项目&#xff1a; 定义对应的表结构&#xff0c;为了学习所以才添加大量的 SQL 规则&#xff0c;要记得针对货币的处理方案 create table if not exists tbl_books( id bigint primary key auto_increment,book_name varchar(32) not null,book_price numeric(8…

专业的PDF文件压缩工具推荐,让你的PDF文件轻松压缩

​在参加专业的比赛时&#xff0c;就需要用到pdf文件&#xff0c;如果pdf文件过大操作和分享起来就特别不方便&#xff0c;其实可以使用专业的pdf文件压缩工具来处理。今天就分享一款pdf在线压缩工具&#xff0c;通过浏览器就可以快速完成pdf压缩&#xff08;https://www.yasuo…

SQL22 统计每个学校的答过题的用户的平均答题数

SELECT university,COUNT(qt.question_id)/COUNT(distinct(qt.device_id)) avg_answer_cnt FROM question_practice_detail qt LEFT JOIN user_profile ut ON qt.device_idut.device_id GROUP BY university

使用Word轻松实现PDF转Word

以前WPS还能通过每天打卡白嫖会员&#xff0c;最近不行了&#xff0c;害&#xff0c;羊毛没了 现在重新回归Word&#xff0c;利用Word就可以将PDF转化为Word 一、通过Word新建一个Word文档并打开 二、点击 文件 —> 打开 三、浏览&#xff0c;找到要转的PDF 四、点击确定&…

基础篇--初识STM32

初识STM32 STM32是什么 ST&#xff1a;意法半导体 M&#xff1a;MCU/MPU32:32位 ST累计推出了&#xff1a;5大类、18个系列、1000多个型号的Cortex内核微控制器 STM32芯片分类 ST中文社区网&#xff1a;https://www.stmcu.org.cn/ ST官网&#xff1a;https://www.st.com …

4.5Java EEMyBatis缓存机制

一、 一级缓存 MyBatis的一级缓存级别 MyBatis的一级缓存是SqlSession级别的缓存。如果同一个SqlSession对象多次执行完全相同的SQL语句时&#xff0c;在第一次执行完成后&#xff0c;MyBatis会将查询结果写入到一级缓存中&#xff0c;此后&#xff0c;如果程序没有执行插入、…

Mysql (insert,update操作)

1.创建表&#xff1a; 创建员工表employee&#xff0c;字段如下&#xff1a; id&#xff08;员工编号&#xff09;&#xff0c;name&#xff08;员工名字&#xff09;&#xff0c;gender&#xff08;员工性别&#xff09;&#xff0c;salary&#xff08;员工薪资&#xff09; …

脚踏Java知识点

对上节Java的基础语法续讲 三元运算符和if语句格式的区别 语法格式&#xff1a; 三元运算符的语法格式是&#xff1a;(condition) ? expression1 : expression2&#xff1b; if语句的语法格式是&#xff1a; if (condition) { // 执行 expression1 } else { // 执行 express…

Stage模型HarmonyOS服务卡片开发整体说明

服务卡片&#xff08;以下简称“卡片”&#xff09;是一种界面展示形式&#xff0c;可以将应用的重要信息或操作前置到卡片&#xff0c;以达到服务直达、减少体验层级的目的。卡片常用于嵌入到其他应用&#xff08;当前卡片使用方只支持系统应用&#xff0c;如桌面&#xff09;…

cyclo(Ser-Ser),23409-30-5,环(L-丝氨酰基-L-丝氨酰),具有明确的生物活性

​资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ 产品描述&#xff1a; cyclo(Ser-Ser)&#xff08;CAS号&#xff1a;23409-30-5&#xff09;&#xff0c;环二肽(2,5-哌嗪二酮)是Z小的环肽&#xff0c;许多天然环二肽化合物都具有明确的生物活性&#xff0c;例如作为抗…

什么是矢量数据库?

我们正处于人工智能革命之中。它颠覆了它所接触的任何行业&#xff0c;承诺了伟大的创新 – 但它也带来了新的挑战。对于涉及大型语言模型、生成式 AI 和语义搜索的应用程序&#xff0c;高效的数据处理变得比以往任何时候都更加重要。 所有这些新应用程序都依赖于向量嵌入&…