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


前言

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

特性

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

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

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

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

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

自定义数据源切换逻辑


一、dynamic-source是什么?

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

二、使用步骤

环境

组件 版本
java 11
springboot 2.5.2
mysql 5.7
postgresql 14.5
dynamic-datasource-spring-boot-starter 3.4.1

1.引入库

maven

        <dependency>
            <groupId>io.github.wangxvdong</groupId>
            <artifactId>dynamic-datasource</artifactId>
            <version>1.0.7</version>
        </dependency>

gradle

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

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

比如我是用的postgresql

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>${postgresql.version}</version>
</dependency>

创建数据源配置表

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();

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 ;

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&currentSchema=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&currentSchema=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

配置数据库数据源项

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

key url username password driver_class_name schema description created_at updated_at
ds-2 jdbc:postgresql://192.168.88.99:5432/ddd?sslmode=disable&currentSchema=ddd&timezone=Asia/Shanghai&allowMultiQueries=true username userpassword org.postgresql.Driver public NULL 2022-09-04 14:03:41.247301 +00:00 2022-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);
    }
}

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

动态切换数据源注解@JdbcDS

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

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

静态切换数据源注解@DS

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

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

嵌套方法切换数据源

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();
    }

总结

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