• 使用数据库维护数据来源,动态切换数据源的工具:dynamic-datasource



    前言

    基于baomidou / dynamic-datasource-spring-boot-starter的采用jdbc自动更新的动态多数据源工具。

    特性

    可采用数据库和配置文件维护数据源

    自动加载、移除数据源(数据来源为数据库方式)

    可以使用接口参数自动切换数据源

    同一数据源下切换schema(对于postgresql用的多)

    多层数据源嵌套切换使用方案

    自定义数据源切换逻辑


    一、dynamic-source是什么?

    dynamic-source是基于dynamic-datasource-spring-boot-starter 的一种工具,该工具是为了解决数据源动态切换而创建的。

    二、使用步骤

    环境

    组件版本
    java11
    springboot2.5.2
    mysql5.7
    postgresql14.5
    dynamic-datasource-spring-boot-starter3.4.1

    1.引入库

    maven

            <dependency>
                <groupId>io.github.wangxvdonggroupId>
                <artifactId>dynamic-datasourceartifactId>
                <version>1.0.7version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    gradle

    implementation 'io.github.wangxvdong:dynamic-datasource:1.0.6'
    
    • 1

    使用的数据库connector依赖也要引入

    比如我是用的postgresql

    <dependency>
        <groupId>org.postgresqlgroupId>
        <artifactId>postgresqlartifactId>
        <version>${postgresql.version}version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建数据源配置表

    postgresql:14.5

    create table datasource_properties
    (
    	key varchar(128) not null
    		constraint datasource_properties_pk
    	                check ( key ~ '^ds-.*$' )
    			primary key,
    	url varchar(1024) not null,
    	username varchar(128) not null,
    	password varchar(256) not null,
    	driver_class_name varchar(128) not null,
    	schema varchar(128),
    	description varchar(1024),
    	created_at timestamp with time zone default CURRENT_TIMESTAMP,
    	updated_at timestamp with time zone
    );
    
    comment on table datasource_properties is '数据源配置表';
    
    comment on column datasource_properties.key is '数据源标识';
    
    comment on column datasource_properties.url is 'jdbc链接';
    
    comment on column datasource_properties.username is '账户名称';
    
    comment on column datasource_properties.password is '账户密码';
    
    comment on column datasource_properties.driver_class_name is '数据库驱动类';
    
    comment on column datasource_properties.schema is '数据库schema,(pgsql schema名称)';
    
    comment on column datasource_properties.description is '数据源描述';
    
    
    CREATE OR REPLACE FUNCTION update_modified_column()
        RETURNS TRIGGER AS $$
    BEGIN NEW.updated_at = now();
    RETURN NEW;
    END;
    $$ language 'plpgsql';
    
    create trigger update_table_name_update_at
        before update
        on datasource_properties
        for each row
        execute procedure update_modified_column();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    mysql:5.7

    create table datasource_properties
    (
        `key` varchar(128) not null comment '数据源标识',
        url varchar(1024) not null comment 'jdbc链接',
        username varchar(128) not null comment '账户名称',
        password varchar(256) not null comment '账户密码',
        driver_class_name varchar(128) not null comment '数据库驱动类',
        `schema` varchar(128) not null comment '数据库schema,(pgsql schema名称)',
        description varchar(1024) null comment '数据源描述',
        created_at timestamp default CURRENT_TIMESTAMP not null ,
        updated_at timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP ,
        constraint datasource_properties_pk
            primary key (`key`),
        check ( `key` like 'ds-%')
    ) comment '数据源配置表';
    
    # 因为mysql8.0以下的版本不支持check 约束,所以为了兼容旧版本提供了下面这个触发器来检查数据源配置key字段的检查
    delimiter //
    create trigger check_datasource_key_prefix before insert on datasource_properties
        for each row
    begin
        if ! ( NEW.key like 'ds-%') then
            signal sqlstate '50000' set message_text = 'datasource_properties column key must to begin by ds- ';
        end if;
    end;
    delimiter ;
    
    delimiter //
    create trigger check_datasource_key_prefix before update on datasource_properties
        for each row
    begin
        if ! ( NEW.key like 'ds-%') then
            signal sqlstate '50000' set message_text = 'datasource_properties column key must to begin by ds- ';
        end if;
    end;
    delimiter ;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    application.yml配置

    spring:datasource:dynamic:datasource:jdbcdatasource: # 指定使用jdbc 数据来源,必须用这个属性名!!!
    配置后则开启JDBC数据源管理逻辑

    spring:
      datasource:
        dynamic:
          primary: master #设置默认的数据源或者数据源组,默认值即为master
          strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
          lazy: true
          datasource:
            master:
              url: jdbc:postgresql://192.168.99.77:5432/ddd?sslmode=disable¤tSchema=ddd&timezone=Asia/Shanghai&allowMultiQueries=true
              username: ddd11
              password: ddd11
              driver-class-name: org.postgresql.Driver # 3.2.0开始支持SPI可省略此配置
              schema: ddd
            jdbcdatasource: # 指定使用jdbc 数据来源,必须用这个属性名!!!
              url: jdbc:postgresql://101.111.222.121:5432/postgres?sslmode=disable¤tSchema=public&timezone=Asia/Shanghai&allowMultiQueries=true
              username: postgres
              password: 123321
              driver-class-name: org.postgresql.Driver # 3.2.0开始支持SPI可省略此配置
              schema: public
    
            #        slave_2:
    #          url: ENC(xxxxx) # 内置加密,使用请查看详细文档
    #          username: ENC(xxxxx)
    #          password: ENC(xxxxx)
    #          driver-class-name: com.mysql.jdbc.Driver
            #......省略
            #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    配置数据库数据源项

    数据库表:datasource_properties
    字段key必须以”ds-“开头

    keyurlusernamepassworddriver_class_nameschemadescriptioncreated_atupdated_at
    ds-2jdbc:postgresql://192.168.88.99:5432/ddd?sslmode=disable¤tSchema=ddd&timezone=Asia/Shanghai&allowMultiQueries=trueusernameuserpasswordorg.postgresql.DriverpublicNULL2022-09-04 14:03:41.247301 +00:002022-09-04 14:03:41.247301 +00:00

    datasource_properties表里新增删除配置,程序里会跟着移除/加载数据源,1分钟更新一次,不会刷新老的数据源连接。只支持新增和删除记录,更新数据库记录没有程序数据源里面不会有动作。

    实现数据源/schema切换逻辑

    继承cn.rocky.dynamicdatasource.service.AbstractCheckoutDataSource就可以让数据源和schema根据自己的逻辑进行切换。
    实现push/poll方法。

    根据dsKey获取DataSourceProperty,比如schema等信息。
    cn.rocky.dynamicdatasource.config.JdbcDynamicDataSourceConfig#getDataSourcePropertyByKey

    代码如下(示例):

    @Service
    public class CustomizeCheckoutDataSourceImpl extends AbstractCheckoutDataSource {
        @Autowired
        private JdbcDynamicDataSourceConfig jdbcDynamicDataSourceConfig;
    
        /**
         * 切入数据源逻辑
         * @param proceedingJoinPoint
         */
        @Override
        public void push(ProceedingJoinPoint proceedingJoinPoint) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String dsKey = request.getHeader("token");
            String schema;
            {
                DataSourceProperty property = jdbcDynamicDataSourceConfig.getDataSourcePropertyByKey(dsKey); // 通过key获取数据源配置
                schema = property.getSchema();
            }
            Stack<String> schemaStack = (Stack<String>) AbstractCheckoutDataSource.getThreadLocal().get();
            if (schemaStack == null) {
                schemaStack = new Stack<>();
            }
            schemaStack.push(schema); // 加入schema限定名
            AbstractCheckoutDataSource.getThreadLocal().set(schemaStack); // 加入schema限定名
    
            DynamicDataSourceContextHolder.push(dsKey); // 切入数据源
        }
    
        /**
         * 切出数据源逻辑
         */
        @Override
        public void poll() {
            DynamicDataSourceContextHolder.poll(); // 切出数据源
            Stack<String> schemaStack = (Stack<String>) AbstractCheckoutDataSource.getThreadLocal().get();
            schemaStack.pop(); // 移除schema限定名
            AbstractCheckoutDataSource.getThreadLocal().set(schemaStack);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    使用注解来标注哪些方法要切换数据源

    动态切换数据源注解@JdbcDS

    此注解会走AbstractCheckoutDataSource 的切换逻辑动态的选择数据源。

        /**
         * 嵌套使用数据源
         */
        @JdbcDS
        public void nestedCheckoutDs(){
            List<Fields> fields = fieldsMapper.selectList(null);
            log.info("jdbc data:{}", fields);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    静态切换数据源注解@DS

    这个注解就是直接切换到指定的数据源。

        /**
         * 手动切数据源
         */
        @DS("ds-2")
        public void useDs2() {
            List<Fields> fields = fieldsMapper.selectList(null);
            log.info("ds2 data:{}", fields);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    嵌套方法切换数据源

    1.在SpringBootApplication类型加入注解@EnableAspectJAutoProxy(exposeProxy = true)

    2.使用AopContext.currentProxy()调用需要被控制数据源的方法

    nestedCheckoutDs方法正常走切换逻辑,但是这个方法里面的对象是被AOP代理invoke的,不会触发AOP这个时候只有被AopContext.currentProxy()取出的对象才能让springAOP生效。

        /**
         * 嵌套使用数据源
         */
        @JdbcDS
        public void nestedCheckoutDs(){
            List<Fields> fields = fieldsMapper.selectList(null);
            log.info("jdbc data:{}", fields);
            ((CheckoutDataSourceServiceImpl) (AopContext.currentProxy())).useDs1();
            ((CheckoutDataSourceServiceImpl) (AopContext.currentProxy())).useDs2();
            useDs1();
            useDs2();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    总结

    以上就是今天要讲的内容,本文简单介绍了dynamic-datasource的使用,而dynamic-datasource提供了能使我们快速便捷地处理数据源切换的方案。

  • 相关阅读:
    一、Redis入门之——介绍、安装,图形化界面(GUI)工具Redis Desktop Manager (RDM)安装
    MySQL 常用函数 2022/09/06
    微任务和宏任务
    KEIL5.39 5.40 fromelf 不能生成HEX bug
    No suitable driver found for jdbc:mysql://localhost:3306/BookManagement
    RestTemplate踩坑 之 ContentType 自动添加字符集
    go服务k8s容器化之grpc负载均衡
    这些大厂笔试题 你都见识(被无情鞭挞)过了吗?—— 瓜子二手车篇
    Acwing算法提高课——背包问题求具体方案
    一起来做个CH347的项目(应用于FPGA、CPLD、MCU)
  • 原文地址:https://blog.csdn.net/wangxudongx/article/details/126692821