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

这篇具有很好参考价值的文章主要介绍了【工作小札】利用动态数据源实现Sass的一种思路(内含完整代码示例)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

✨这里是第七人格的博客✨小七,欢迎您的到来~✨
🍅系列专栏:【工作小札】🍅
✈️本篇内容: 利用动态数据源实现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 添加数据库

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

4.5.2 初始化数据库

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

初始化后,可在本地库中看到新建的数据库
【工作小札】利用动态数据源实现Sass的一种思路(内含完整代码示例)

修改area表area_name的数据为重庆测试
【工作小札】利用动态数据源实现Sass的一种思路(内含完整代码示例)

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

4.5.2 多租户测试

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

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

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

模拟业务请求
【工作小札】利用动态数据源实现Sass的一种思路(内含完整代码示例)文章来源地址https://www.toymoban.com/news/detail-419535.html

到了这里,关于【工作小札】利用动态数据源实现Sass的一种思路(内含完整代码示例)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • spring多数据源动态切换的实现原理及读写分离的应用

    AbstractRoutingDataSource 是Spring框架中的一个抽象类,可以实现多数据源的动态切换和路由,以满足复杂的业务需求和提高系统的性能、可扩展性、灵活性。 多租户支持:对于多租户的应用,根据当前租户来选择其对应的数据源,实现租户级别的隔离和数据存储。 分库分表:为了

    2024年02月14日
    浏览(39)
  • 一种实现Spring动态数据源切换的方法 | 京东云技术团队

    不在现有查询代码逻辑上做任何改动,实现dao维度的数据源切换(即表维度) 节约bdp的集群资源。接入新的宽表时,通常uat验证后就会停止集群释放资源,在对应的查询服务器uat环境时需要查询的是生产库的表数据(uat库表因为bdp实时任务停止,没有数据落入),只进行服务

    2024年02月10日
    浏览(58)
  • 如何在Spring Boot应用中使用Nacos实现动态更新数据源

    🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通Golang(基础入门篇)》学会Golang语言

    2024年02月10日
    浏览(41)
  • 通讯网关软件031——利用CommGate X2HTTP实现HTTP访问ODBC数据源

    本文介绍利用CommGate X2HTTP实现HTTP访问ODBC数据源。CommGate X2HTTP是宁波科安网信开发的网关软件,软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示,实现上位机通过HTTP来获取ODBC数据源的数据。 【解决方案】设置网关机,与ODBC 数据源采用以太网通讯,与

    2024年02月06日
    浏览(41)
  • 【Spring AOP + 自定义注解 + 动态数据源 实现主从库切换&读写分离】—— 案例实战

                                                 💧 S p r i n g A O P + 主从数据源切换 + 读写分离 + 自定义注解案例实战! color{#FF1493}{Spring AOP + 主从数据源切换 + 读写分离 + 自定义注解 案例实战!} Sp r in g A OP + 主从数据源切换 + 读写分离 + 自定义注解案例

    2024年02月15日
    浏览(39)
  • Spring Boot + MyBatis-Plus 实现 MySQL 主从复制动态数据源切换

    MySQL 主从复制是一种常见的数据库架构,它可以提高数据库的性能和可用性。 动态数据源切换则可以根据业务需求,在不同场景下使用不同的数据源,比如在读多写少的场景下,可以通过切换到从库来分担主库的压力 。 在本文中,我们将介绍如何在 Spring Boot 中实现 MySQL 动

    2024年02月19日
    浏览(62)
  • 利用Re新增数据源dis实现向量相似度搜索:解决文本、图像和音频之间的相似度匹配问题

    最近工作中需要用到MongoDB的事务操作,因此参考了一些资料封装了一个小的组件,提供基础的CRUD Repository基类 和 UnitOfWork工作单元模式。今天,就来简单介绍一下这个小组件。 MongoDB在4.2版本开始全面支持了多文档事务,至今已过了四年了,虽然我们可能没有在项目中用Mon

    2024年01月23日
    浏览(44)
  • SpringBoot——动态数据源(多数据源自动切换)

    日常的业务开发项目中只会配置一套数据源,如果需要获取其他系统的数据往往是通过调用接口, 或者是通过第三方工具比如kettle将数据同步到自己的数据库中进行访问。 但是也会有需要在项目中引用多数据源的场景。比如如下场景: 自研数据迁移系统,至少需要新、老两

    2024年02月16日
    浏览(40)
  • springboot整合多数据源的配置以及动态切换数据源,注解切换数据源

    在许多应用程序中,可能需要使用多个数据库或数据源来处理不同的业务需求。Spring Boot提供了简便的方式来配置和使用多数据源,使开发人员能够轻松处理多个数据库连接。如果你的项目中可能需要随时切换数据源的话,那我这篇文章可能能帮助到你 ℹ️:这里对于pom文件

    2024年02月10日
    浏览(54)
  • springboot dynamic-datasource 实现动态切换数据源-多租户-配置文件切换-基于dynamic-datasource

    1、实现动态切换数据源 2、实现配置多数据源 3、实现读写分离也可以用多数据源方式 4、选择 dynamic-datasource集成了很多ORM的框架,其中,使用比较多的是druid,但有一些东西开始收费了 druid也可以自行配置,配置多了点 目前版本只支持单一位置加载数据源(只能从配置文件或

    2024年02月09日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包