动态数据配置实现两种方式

前言

最近接到一个需求就是动态配置数据源,这个数据源个数未知,但是有一个默认的数据源。

传统动态数据源配置方法

1.引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>2.5.6</version>
</dependency>
复制代码

2.写入数据源配置

spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      datasource:
        master:
          username: 你的数据库用户名
          password: 你的用户名密码
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://xx.xx.xx/xx?useUnicode=true&characterEncoding=utf-8&useSSL=false
        slave: #这个就是从库
          username: 你的数据库用户名
          password: 你的数据库用户名
          driver-class-name: com.p6spy.engine.spy.P6SpyDriver
          url: jdbc:p6spy:mysql://xxx.xxx.xxx/xxx?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
          ... #还可以配置Oracle数据库


复制代码

3.使用

/**
 * @author zly
 * @version 1.0.0
 * @ClassName HisTaskServiceImpl.java
 * @Description TODO
 * @createTime 2021年05月25日 11:49:00
 */
@Service("hisTaskService")
@DS("slave") //这个service使用的都是salve数据源 spring boot 启动已经将数据源加载进去 注解也可以写在方法上面
@Slf4j
public class HisTaskServiceImpl implements HisTaskService {
....
}
复制代码

等等这个方法似乎是实现不了一开始说的需求就是咱不知道有多少个数据源,就没有办法再配置文件中配好所以还有一种方式

第二种方式

1.同样引入依赖

 <dependency>
           <groupId>com.github.yeecode.dynamicdatasource</groupId>
           <artifactId>DynamicDataSource</artifactId>
           <version>1.3.2</version>
       </dependency>
复制代码

轮子git地址
代码先不展开来讲了 两种凡是都是基于AbstractRoutingDataSource实现这个类来写,有轮子咱就不造了站在巨人肩膀上看风景吧

2.配置默认的数据源

dynamicDataSource:
  default:
    url: 数据库地址
    username: 数据库用户名
    password: 数据库密码
    driverClassName: com.p6spy.engine.spy.P6SpyDriver
复制代码

配置方式引入依赖包的方式都是这么配的,不过这些代码都是开源的可以根据自己的项目实际改动一下

3.使用

@RestController
public class MainController {
    @Autowired
    private UserService userService;
    @Autowired
    private DynamicDataSource dynamicDataSource;//注入动态数据源

    @RequestMapping(value = "/01")
    public String queryFromDS01() {
        DataSourceInfo dataSourceInfo = new DataSourceInfo("ds01",
                "com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://xxx.xxx.xxx/db1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8",
                "root",
                "password");
        dynamicDataSource.addDataSource(dataSourceInfo, true);
        dynamicDataSource.switchDataSource("ds01");
        return userService.select();
    }

    @RequestMapping(value = "/02")
    public String queryFromDS02() {
        DataSourceInfo dataSourceInfo = new DataSourceInfo("ds02",
                "com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://xxx.xxx.xxx:port/db2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8",
                "root",
                "password");
        dynamicDataSource.addAndSwitchDataSource(dataSourceInfo, true);
        return userService.select();
    }

复制代码

这种可以使用注入的方式注入进来,通过接口传入数据源信息来实现页面配置数据源进行动态转换

4.DynamicDataSource 源码

package com.github.yeecode.dynamicdatasource;

import com.github.yeecode.dynamicdatasource.datasource.DynamicDataSourceConfig;
import com.github.yeecode.dynamicdatasource.model.DataSourceInfo;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.concurrent.ConcurrentHashMap;

public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal<String> CURRENT_DATASOURCE_NAME = new ThreadLocal<String>();
    private ConcurrentHashMap<Object, Object> dataSourcesMap = new ConcurrentHashMap<Object, Object>();
    private ConcurrentHashMap<Object, DataSourceInfo> dataSourceInfoMap = new ConcurrentHashMap<Object, DataSourceInfo>();
    private final String DEFAULT_DATA_SOURCE = "com.github.yeecode.dynamicdatasource_defaultDataSource";

    public DynamicDataSource(DataSource defaultDataSource) {
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(dataSourcesMap);
        this.dataSourcesMap.put(DEFAULT_DATA_SOURCE, defaultDataSource);
    }

    @Override
    public Object determineCurrentLookupKey() {
        return CURRENT_DATASOURCE_NAME.get();
    }

    /**
     *
     *
     * @param dataSourceInfo 创建新的DataSource
     * @param overwrite      是否允许在已存在同名数据源时覆盖
     * @return 是否已经添加新的数据源
     */
    public synchronized boolean addDataSource(DataSourceInfo dataSourceInfo, Boolean overwrite) {
        if (dataSourcesMap.containsKey(dataSourceInfo.getName()) && dataSourceInfo.equals(dataSourceInfoMap.get(dataSourceInfo.getName()))) {
            return true;
        } else if (dataSourcesMap.containsKey(dataSourceInfo.getName()) && !overwrite) {
            return false;
        } else {
            DataSource dataSource = DynamicDataSourceConfig.createDataSource(dataSourceInfo);
            dataSourcesMap.put(dataSourceInfo.getName(), dataSource);
            dataSourceInfoMap.put(dataSourceInfo.getName(), dataSourceInfo);
            super.afterPropertiesSet();
            return true;
        }
    }

    /**
     * 添加一个新的数据源并切换到它
     *
     * @param dataSourceInfo 新的数据源名称
     * @param overwrite      如果存在数据源就覆盖已有的
     * @return 新的数据源是否已经可用
     */
    public synchronized boolean addAndSwitchDataSource(DataSourceInfo dataSourceInfo, Boolean overwrite) {
        if (dataSourcesMap.containsKey(dataSourceInfo.getName()) && dataSourceInfo.equals(dataSourceInfoMap.get(dataSourceInfo.getName()))) {
            CURRENT_DATASOURCE_NAME.set(dataSourceInfo.getName());
            return true;
        } else if (dataSourcesMap.containsKey(dataSourceInfo.getName()) && !overwrite) {
            return false;
        } else {
            DataSource dataSource = DynamicDataSourceConfig.createDataSource(dataSourceInfo);
            dataSourcesMap.put(dataSourceInfo.getName(), dataSource);
            dataSourceInfoMap.put(dataSourceInfo.getName(),dataSourceInfo);
            super.afterPropertiesSet();
            CURRENT_DATASOURCE_NAME.set(dataSourceInfo.getName());
            return true;
        }
    }

    /**
     * 切换数据源
     *
     * @param dataSourceName 切换的数据源
     * @return 是否切换成功
     */
    public synchronized boolean switchDataSource(String dataSourceName) {
        if (!dataSourcesMap.containsKey(dataSourceName)) {
            return false;
        }
        CURRENT_DATASOURCE_NAME.set(dataSourceName);
        return true;
    }

    /**
     * 按名称删除数据源。如果指定的数据源正在使用中,则不允许删除。
     *
     * @param dataSourceName 要删除的数据源名称
     * @return 删除是否成功
     */
    public synchronized boolean delDataSource(String dataSourceName) {
        if (CURRENT_DATASOURCE_NAME.get().equals(dataSourceName)) {
            return false;
        } else {
            dataSourcesMap.remove(dataSourceName);
            dataSourceInfoMap.remove(dataSourceName);
            return true;
        }
    }

    /**
     * 获取默认数据源
     *
     * @return 默认数据源
     */
    public DataSource getDefaultDataSource() {
        return (DataSource) dataSourcesMap.get(DEFAULT_DATA_SOURCE);
    }

    /**
     * 切换默认数据源
     */
    public void switchDefaultDataSource() {
        CURRENT_DATASOURCE_NAME.set(DEFAULT_DATA_SOURCE);
    }

}

复制代码

启动加载默认数据源配置类

package com.github.yeecode.dynamicdatasource.datasource;

import com.github.yeecode.dynamicdatasource.DynamicDataSource;
import com.github.yeecode.dynamicdatasource.model.DataSourceInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class DynamicDataSourceConfig {

    @Value("${dynamicDataSource.default.url}")
    private String defaultUrl;
    @Value("${dynamicDataSource.default.driverClassName}")
    private String driverClassName;
    @Value("${dynamicDataSource.default.username}")
    private String defaultUsername;
    @Value("${dynamicDataSource.default.password}")
    private String defaultPassword;

    @Bean("defaultDataSource")
    public DataSource defaultDataSource() {
        return DataSourceBuilder.create().url(defaultUrl)
                .driverClassName(driverClassName)
                .username(defaultUsername)
                .password(defaultPassword).build();
    }

    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource(DataSource defaultDataSource) {
        return new DynamicDataSource(defaultDataSource);
    }

    public static DataSource createDataSource(DataSourceInfo datasourceInfo) {
        return DataSourceBuilder.create().url(datasourceInfo.getUrl())
                .driverClassName(datasourceInfo.getDriverClassName())
                .username(datasourceInfo.getUserName())
                .password(datasourceInfo.getPassword()).build();
    }

}

复制代码

5.注意点

要在启动类屏蔽加载数据源加上和扫描动态数据源配置类 如果自己更改类配置 包名路径自行改动

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@ComponentScan(basePackages = {"com.github.yeecode.dynamicdatasource","这个是自己的项目路经包名"})
复制代码

这篇只是介绍两种使用动态数据源的方法,并没有对源码进行讲解 q_q

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享