【工作小札】利用动态数据源实现Sass的一种思路(内含完整代码示例)

news2024/11/24 8:36:44

文章目录

  • 1 楔子
  • 2 分析
  • 3 代码实现
    • 3.1 管理库关键库表设计
    • 3.2 租户库关键库表设计
    • 3.3 新建一个SpringBootWeb项目
    • 3.4 添加maven依赖
    • 3.5 创建初始化数据库工具类
    • 3.6 创建动态数据源配置类
    • 3.7 创建登录代码
    • 3.8 创建数据源元数据服务类
    • 3.9 创建saas服务基础父类
  • 4 示例演示
    • 4.1 下载示例代码
    • 4.2 执行resources下初始化数据库脚本init.sql
    • 4.3 修改resources下application.yml中数据库配置
    • 4.4 通过DynamicApplication启动项目
    • 4.5 测试
      • 4.5.1 添加数据库
      • 4.5.2 初始化数据库
      • 4.5.2 多租户测试

✨这里是第七人格的博客✨小七,欢迎您的到来~✨
🍅系列专栏:【工作小札】🍅
✈️本篇内容: 利用动态数据源实现Sass化✈️
🍱本篇收录完整代码地址:https://gitee.com/diqirenge/sheep-web-demo/tree/master/sheep-web-demo-dynamicDataSource🍱

1 楔子

针对Sass多租户,业内有许多解决方案。一般来说,如果做的简单一点,直接用一个表字段区分租户,所有db操作都带上这个标识即可。如果做的稍微好一点,我们可以考虑分库,即每个租户都拥有自己的数据库,且可以将数据库部署在本地。

2 分析

基于分库的需求,我们可以做以下技术拆分:

1、需要有一个管理中心,管理所有租户的数据库,这个应该是一个单独的库,租户的库又是其他单独的库。

2、从管理中心页面上,要能够对租户的库进行管理,比如动态建库建表。

3、后台只用一套代码,所以要动态适配数据源。

4、租户登录之后,应该就要适配到适合自己的库。

3 代码实现

以下是关键代码的实现,如果读者不感兴趣,可以直接看第4章。

3.1 管理库关键库表设计

库名随意,我这里取dynamic

CREATE DATABASE `dynamic` ;

作为管理库,肯定要管理其他库的数据库元数据,那么抽象出哪些元数据比较合适呢?观察以下配置

url: jdbc:mysql://localhost:3306/dynamic?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: root
password: 123456

我们发现连接Mysql时,需要配置url、username以及password,为了方便切库我们多抽象设计一个schema(即问号前面的dynamic部分)。这里给出一个简单的参考表如下:

CREATE TABLE `data_source_meta`  (
                                 `id` int(0) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
                                 `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '名称',
                                 `url` varchar(127) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'mysql地址',
                                 `mysql_schema` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'mysql库名',
                                 `user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'mysql用户名',
                                 `user_password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'mysql密码',
                                 PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

3.2 租户库关键库表设计

租户主要是业务表,我们这里就随便设计一个地区表area

CREATE TABLE `area`  (
                         `area_id` int(0) NOT NULL AUTO_INCREMENT,
                         `area_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
                         PRIMARY KEY (`area_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

3.3 新建一个SpringBootWeb项目

3.4 添加maven依赖

为了实现我们的需求,需要添加以下2个关键依赖

    <!--动态数据源-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.5.0</version>
    </dependency>
    <!--加入数据库连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.9</version>
    </dependency>

3.5 创建初始化数据库工具类

/**
 * 初始化数据库工具
 *
 * @author 第七人格
 * @date 2023/04/13
 */
@Slf4j
public class InitDBUtil {

    /**
     * jdbc url模板
     */
    private static final String jdbcUrlTemplate = "jdbc:mysql://#{mysqlUrl}/#{schema}?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
    /**
     * 驱动程序类
     */
    private static final String driverClass = "com.mysql.cj.jdbc.Driver";

    /**
     * 删除sql模板
     */
    private static final String dropSchemaSqlTemplate = "DROP DATABASE IF EXISTS #{schema}";
    /**
     * 创建sql模板
     */
    private static final String createSchemaSqlTemplate = "CREATE DATABASE `#{schema}` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'; ";
    /**
     * 使用sql模板
     */
    private static final String useSchemaSqlTemplate = "use `#{schema}`;";

    /**
     * 初始化数据库
     *
     * @param mysqlUrl mysql url
     * @param schema   模式
     * @param username 用户名
     * @param password 密码
     * @return boolean
     */
    public static boolean initDB(String mysqlUrl,String schema,String username,String password){
        Connection connection = null;
        try{
            Class.forName(driverClass);
            connection = DriverManager.getConnection(jdbcUrlTemplate.replace("#{mysqlUrl}",mysqlUrl).replace("#{schema}","mysql"), username, password);
            Statement statement = connection.createStatement();
            statement.execute(dropSchemaSqlTemplate.replace("#{schema}",schema));
            statement.execute(createSchemaSqlTemplate.replace("#{schema}",schema));
            statement.execute(useSchemaSqlTemplate.replace("#{schema}",schema));

            ScriptRunner scriptRunner = new ScriptRunner(connection);
            scriptRunner.setStopOnError(true);

            ClassPathResource classPathResource = new ClassPathResource("sqlTemplate.sql");
            InputStream inputStream = classPathResource.getInputStream();
            InputStreamReader isr = new InputStreamReader(inputStream);
            scriptRunner.runScript(isr);
            return true;
        }catch(Exception e){
            log.error("初始化数据库失败,{}",e.getMessage());
            return false;
        }finally {
            if(null != connection){
                try {
                    connection.commit();
                    connection.close();
                } catch (SQLException ignored) {
                }
            }
        }
    }

    public static boolean tryConnectDB(String mysqlUrl,String schema,String username,String password){
        Connection connection = null;
        try{
            Class.forName(driverClass);
            connection = DriverManager.getConnection(jdbcUrlTemplate.replace("#{mysqlUrl}",mysqlUrl).replace("#{schema}",schema), username, password);
            return true;
        }catch(Exception e){
            log.error("尝试连接数据库失败,{}",e.getMessage());
            return false;
        }finally {
            if(null != connection){
                try {
                    connection.commit();
                    connection.close();
                } catch (SQLException ignored) {
                }
            }
        }
    }

    /**
     * 得到初始化数据库配置
     *
     * @return {@link DruidDataSource}
     */
    public static DruidDataSource getInitDBConfig(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setInitialSize(5);
        dataSource.setMinIdle(5);
        dataSource.setMaxActive(20);
        dataSource.setMaxWait(60000);
        dataSource.setTimeBetweenEvictionRunsMillis(60000);
        dataSource.setMinEvictableIdleTimeMillis(300000);
        dataSource.setValidationQuery("select 1 from dual");
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);
        dataSource.setPoolPreparedStatements(true);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
        return dataSource;
    }
}

3.6 创建动态数据源配置类

关键代码是 DynamicRoutingDataSource 的 api 的使用

/**
 * 动态数据源配置
 *
 * @author 第七人格
 * @date 2023/04/13
 */
@Component
@Slf4j
public class DynamicDataSourceConfig {

    /**
     * 缓存
     */
    private final Map<String, String> cache = new HashMap<>();

    /**
     * 数据源
     */
    @Resource
    private DynamicRoutingDataSource dataSource;

    /**
     * 加载所有数据库
     */
    @PostConstruct
    public void loadAllDB(){
        cache.put("master","管理中心");
        // todo 这里可以做成,项目一启动就去读取管理库的数据库元数据,加载到缓存之中
    }

    /**
     * 动态添加数据库
     *
     * @param datasourceMeta 数据源元
     */
    public void addDB(DataSourceMeta datasourceMeta){
        DruidDataSource tmpdb = InitDBUtil.getInitDBConfig();
        tmpdb.setUsername(datasourceMeta.getUsername());
        tmpdb.setPassword(datasourceMeta.getPassword());
        tmpdb.setUrl("jdbc:mysql://"+ datasourceMeta.getUrl()+"/"+ datasourceMeta.getMysqlSchema()+"?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true");
        dataSource.addDataSource(datasourceMeta.getMysqlSchema(),tmpdb);
        log.info("======动态添加数据库完成:mysqlSchema={}",datasourceMeta.getMysqlSchema());
        cache.put(datasourceMeta.getMysqlSchema(), datasourceMeta.getName());
    }

    /**
     * 动态删除数据库
     *
     * @param datasourceMeta 数据源元
     */
    public void deleteDB(DataSourceMeta datasourceMeta){
        dataSource.removeDataSource(datasourceMeta.getMysqlSchema());
        log.info("======动态删除数据库完成:mysqlSchema={}",datasourceMeta.getMysqlSchema());
        cache.remove(datasourceMeta.getMysqlSchema());
    }

    /**
     * 通过schema获取在元数据中的名称
     *
     * @param schema 模式
     * @return {@link String}
     */
    public String getNameBySchema(String schema){
        return cache.getOrDefault(schema, "");
    }
}

3.7 创建登录代码

关键代码是将前端传入的schema,放到浏览器session中

/**
 * 登录控制器
 *
 * @author 第七人格
 * @date 2023/04/13
 */
@RestController
@RequestMapping(value = "/admin")
public class LoginController {
    /**
     * 登录
     *
     * @param schema  模式
     * @param request 请求
     * @return {@link String}
     */
    @GetMapping("/login/{schema}")
    public String login(@PathVariable String schema, HttpServletRequest request) {
        // 存入session,用于切库
        request.getSession().setAttribute("schema",schema);
        return "登录成功!";
    }
}

3.8 创建数据源元数据服务类

关键代码是使用com.baomidou.dynamic.datasource.annotation.DS注解

@DS(“master”),标明使用的是管理库

/**
 * 数据源元数据服务
 *
 * @author 第七人格
 * @date 2023/04/13
 */
@Service
@DS("master")
public class DataSourceMetaService {

    /**
     * 数据源元映射器
     */
    @Resource
    private DataSourceMetaMapper datasourceMetaMapper;

    /**
     * 选择可用数据
     *
     * @param dataSourceMeta 数据源元
     * @return {@link List}<{@link DataSourceMeta}>
     */
    public List<DataSourceMeta> selectAvailable(DataSourceMeta dataSourceMeta) {
        return new LambdaQueryChainWrapper<>(datasourceMetaMapper)
                .eq(DataSourceMeta::getId, dataSourceMeta.getId())
                .eq(DataSourceMeta::getUrl, dataSourceMeta.getUrl())
                .list();
    }

    public void add(DataSourceMeta dataSourceMeta) {
        datasourceMetaMapper.insert(dataSourceMeta);
    }

    public void update(DataSourceMeta dataSourceMeta) {
        datasourceMetaMapper.updateById(dataSourceMeta);
    }

    public void delete(int dataSourceMetaId) {
        datasourceMetaMapper.deleteById(dataSourceMetaId);
    }

    public boolean initDB(DataSourceMeta dataSourceMeta) {
        return InitDBUtil.initDB(dataSourceMeta.getUrl(),dataSourceMeta.getMysqlSchema(),dataSourceMeta.getUsername(),dataSourceMeta.getPassword());
    }

    public boolean tryConnectDB(DataSourceMeta dataSourceMeta) {
        return InitDBUtil.tryConnectDB(dataSourceMeta.getUrl(),dataSourceMeta.getMysqlSchema(),dataSourceMeta.getUsername(),dataSourceMeta.getPassword());
    }
}

3.9 创建saas服务基础父类

关键代码师使用com.baomidou.dynamic.datasource.annotation.DS注解

@DS(“#session.schema”), 该接口下的所有数据操作默认根据session中的schema进行路由,其他业务服务类都要继承他

/**
 * saas服务
 * 该接口下的所有数据操作默认根据session中的schema进行路由。
 *
 * @author 第七人格
 * @date 2023/04/13
 */
@DS("#session.schema")
public class SaasService {
}

业务实现类例子

/**
 * 区域服务impl
 *
 * @author 第七人格
 * @date 2023/04/13
 */
@Service
public class AreaServiceImpl extends SaasService {
    /**
     * 区域映射器
     */
    @Resource
    private AreaMapper areaMapper;

    /**
     * 选择所有
     *
     * @param area 区域
     * @return {@link List}<{@link Area}>
     */
    public List<Area> selectAll(Area area) {
        return new LambdaQueryChainWrapper<>(areaMapper)
                .eq(Area::getAreaId, area.getAreaId())
                .list();
    }
}

4 示例演示

4.1 下载示例代码

https://gitee.com/diqirenge/sheep-web-demo/tree/master/sheep-web-demo-dynamicDataSource

4.2 执行resources下初始化数据库脚本init.sql

4.3 修改resources下application.yml中数据库配置

4.4 通过DynamicApplication启动项目

4.5 测试

测试方法皆可在http-test-api.http文件中查看

4.5.1 添加数据库

在这里插入图片描述

4.5.2 初始化数据库

初始化数据库dy_test_1
在这里插入图片描述

初始化后,可在本地库中看到新建的数据库
在这里插入图片描述

修改area表area_name的数据为重庆测试
在这里插入图片描述

初始化数据库dy_test
在这里插入图片描述

4.5.2 多租户测试

模拟dy_test登录
在这里插入图片描述

模拟业务请求
在这里插入图片描述

模拟dy_test_1登录
在这里插入图片描述

模拟业务请求
在这里插入图片描述

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

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

相关文章

MultiHeadAttention多头注意力机制的原理

MultiHeadAttention多头注意力作为Transformer的核心组件&#xff0c;其主要由多组自注意力组合构成。 1. self-Attention自注意力机制 在NLP任务中&#xff0c;自注意力能够根据上下文词来重新构建目标词的表示&#xff0c;其之所以被称之为注意力&#xff0c;在于从上下文词…

【Spring6】| Spring6集成MyBatis3.5

目录 一&#xff1a;Spring6集成MyBatis3.5 第一步&#xff1a;准备数据库表 第二步&#xff1a;IDEA中创建一个模块&#xff0c;并引入依赖 第三步&#xff1a;基于三层架构实现&#xff0c;所以提前创建好所有的包 第四步&#xff1a;编写pojo 第五步&#xff1a;编写m…

【Redis数据库】异地公网远程登录连接Redis教程

文章目录 1. Linux(centos8)安装redis数据库2. 配置redis数据库3. 内网穿透3.1 安装cpolar内网穿透3.2 创建隧道映射本地端口 4. 配置固定TCP端口地址4.1 保留一个固定tcp地址4.2 配置固定TCP地址4.3 使用固定的tcp地址连接 转发自CSDN远程穿透的文章&#xff1a;公网远程连接R…

Java阶段二Day05

Java阶段二Day05 文章目录 Java阶段二Day05截至此版本可实现的流程图为V14UserControllerClientHandlerDispatcherServletHttpServletResponseHttpServletRequest V15DispatcherServletHttpServletResponseHttpServletRequest V16HttpServletRequestHttpServletResponse 反射JA…

SpringCloud整合AOP做日志管理

目录 1、前置知识2、步骤2.1、依赖2.2、自定义注解&#xff0c;用于注解式AOP2.3、定制切面类2.4、测试 1、前置知识 切面&#xff08;Aspect&#xff09;&#xff1a;官方的抽象定义为“一个关注点的模块化&#xff0c;这个关注点可能会横切多个对象”&#xff0c;在本例中&a…

超详细Redis入门教程——Redis命令(上)

前言 本文小新为大家带来 超详细Redis入门教程——Redis命令&#xff08;上&#xff09; 相关知识&#xff0c;具体内容包括Redis 基本命令&#xff0c;Key 操作命令&#xff0c;String 型 Value 操作命令&#xff0c;Hash 型 Value 操作命令&#xff0c;List 型 Value 操作命令…

快速搭建外卖配送服务:利用外卖系统源码实现

外卖配送服务已经成为了现代消费者生活的一部分&#xff0c;它不仅方便了消费者的用餐需求&#xff0c;也给商家提供了新的销售渠道&#xff0c;同时也为外卖配送员提供了更多的就业机会。为了满足这个市场的需求&#xff0c;外卖系统源码应运而生。 外卖系统源码是一个集成了…

第一章:数、式、方程与方程组

1.实数 1.内容概述 1.了解实数分类2.数轴3.相反数和倒数4.绝对值5.算数平方根相关概念及有关计算2.实数分类 3.实数的基本概念 1.数轴:规定原点、正方向和单位长度的直线叫做数轴2.相反数:绝对值相同而符号相反的两个数,互称相反数3.倒数:1除以任何数的商,我们叫做倒数,0…

超市购物系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87682510 更多系统资源库…

Jenkins ssh windows 部署 java程序

版权说明&#xff1a; 本文由博主keep丶原创&#xff0c;转载请保留该段内容在文章头部。 原文地址&#xff1a; https://blog.csdn.net/qq_38688267/article/details/130203785 文章目录 前言实现步骤1. windows下载安装ssh2. windows 安装 winsw2.1 下载 winsw2.2 配置winsw2…

Linux 0.11启动过程分析(一)

Linux 0.11 系列文章 Linux 0.11启动过程分析&#xff08;一&#xff09; Linux 0.11 fork 函数&#xff08;二&#xff09; Linux0.11 缺页处理&#xff08;三&#xff09; Linux0.11 根文件系统挂载&#xff08;四&#xff09; Linux0.11 文件打开open函数&#xff08;五&…

[oeasy]python0132_变量含义_meaning_声明_declaration_赋值_assignment

变量定义 回忆上次内容 上次回顾了一下历史 python 是如何从无到有的看到 Guido 长期的坚持和努力 编程语言的基础都是变量声明 python是如何声明变量的呢&#xff1f; 变量 想要定义变量首先明确什么是变量 变量就是数值能变的量英文名称 variable 计算机在内存中分配出…

SpringBoot Starter 作用及原理

本文会以 mybatis 为例&#xff0c;通过对比 mybatis-spring 和 mybatis-spring-boot-starter 代码示例&#xff0c;了解 Starter 的作用。并对 mybatis-spring-boot-starter 进行简单剖析&#xff0c;了解 Starter 原理。 下面还有投票&#xff0c;一起参与进来吧&#x1f44d…

DataEase看中国 - 中国影星“成龙”电影票房数据分析

背景介绍 说起成龙&#xff0c;我们并不陌生&#xff0c;著名的动作明星。以武打动作片出道&#xff0c;凭借动作片《红番区》打入好莱坞&#xff0c;该片打破北美外语片票房纪录。 目前&#xff0c;由成龙、郭麒麟等主演的新片《龙马精神》正在公映&#xff0c;电影《…

【每日一练】JAVA算法求柱状图中最大的矩形面积

文章目录 前言题目分析算法实战1、创建算法方法2、创建测试用例3、查看测试结果 写在最后 前言 作为一名以JAVA语言为主的搬砖人&#xff0c;学习掌握好函数语法很重要&#xff0c;但是算法也是需要掌握的。今天我们就分享一个求柱状图中最大的矩形面积的题目&#xff0c;这个…

torch.utils.data.DataLoader中的next(iter(train_dataloader))

在做实验时&#xff0c;我们常常会使用用开源的数据集进行测试。而Pytorch中内置了许多数据集&#xff0c;这些数据集我们常常使用DataLoader类进行加载。 如下面这个我们使用DataLoader类加载torch.vision中的FashionMNIST数据集。 from torch.utils.data import DataLoader …

数据结构入门(C语言)顺序表的增删查改

目录 前言1. 顺序表的概念2. 动态顺序表2.1 顺序表的初始化与销毁2.2 顺序表的尾插容量检查2.3 顺序表的尾删2.4 顺序表的头插2.5 顺序表的头删2.6 固定位置的插入2.7 固定位置的删除2.8 查找和打印2.9 修改元素主函数部分(菜单) 结语 前言 本章会用C语言来描述数据结构中的顺…

协同运力、算力、存力,加速迈向智能世界

2023年4月20日&#xff0c;华为在HAS2023期间举办“迈向智能世界”主题论坛&#xff0c;吸引了来自全球的分析师、专家学者及媒体与会。会上&#xff0c;华为ICT战略与Marketing总裁彭松发表了“持续技术创新&#xff0c;加速迈向智能世界”的主题演讲。 华为ICT战略与Marketin…

zabbix监控linux主机

1.本实验使用centos7主机&#xff0c;IP地址为10.1.60.115&#xff0c;firewalld和selinux服务已关闭 2.下载zabbix yum源(与zabbix server用一样的版本) rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm 3.安装zabbix客户…

玛雅水上乐园|玩趣系列作品集

玛雅水上乐园曾经是一座历史悠久的玛雅金字塔&#xff0c;曾用于宗教和水上航行&#xff0c;被废弃了 3000 多年。现在&#xff0c;01a1 工作室已将其改造成一个令人兴奋的旅游景点&#xff0c;在这里你可以享受美食和饮料&#xff0c;享受日光浴&#xff0c;并结交新朋友。所以…