springboot多数据源, 动态数据源实现

springboot多数据源, 动态数据源实现

背景: 现在随着数据量,业务量的增多,很多情况下,单个数据库已无 法满足项目需求,此时可能需要配置不同的数据源来满足需求,下面介绍基于springboot的多数据源和动态数据源的实现

1. 多数据源

介绍: 基于springboot的多数据源配置,此处可以直接使用mp提供的方法来实现,简单便捷

  1. 引入pom依赖
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>
  1. yml文件配置数据源
 spring:
   application:
     name: my-manage-api-provider
   datasource:
     dynamic:
       primary: manage
       datasource:
         manage:
           url: jdbc:oracle:thin:@127.0.0.1:1521/database
           username: xxxx
           password: 123456
         cs:
           url: jdbc:oracle:thin:@127.0.0.1:1521/database
           username: yyyy
           password: 123456
   druid:
     db-type: oracle
  1. dao层类使用@DS(“数据源key”)即可切换,如@DS(“cs”), 不写就是用的primary设置的数据源

2. 动态数据源

介绍: 随着项目的扩展,例如项目中需要数据库连接信息动态获取,不能在yml文件配置固定了,多数据源可能已经无法满足需求了,所以这就需要我们动态的创建connection来实现

基于springboot的动态数据源,可以通过继承AbstractRoutingDataSource类实现,此处主要包括4个类:
DatasourceConfig
DynamicDataSourceContextHolder
DynamicDataSource
SwitchDb

  1. springboot启动类上添加
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  1. 配置DatasourceConfig
@Configuration
public class DatasourceConfig {

    @ConfigurationProperties(prefix = "spring.datasource.druid")
    @Bean
    public DataSource druidDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource() {
        DataSource druidDataSource = druidDataSource();
        DynamicDataSource dynamicDataSource = DynamicDataSource.getInstance();
        dynamicDataSource.setDefaultTargetDataSource(druidDataSource);
        Map<Object, Object> targetDataSources = new HashMap<>(1);
        targetDataSources.put("default", druidDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.put("default",druidDataSource);
        System.out.println("DataSourceConfig.dynamicDataSource");
        return dynamicDataSource;
    }
}

此处是向datasource添加默认的初始化数据源,其中设置setTargetDataSources()和setDefaultTargetDataSource()是两个最重要的方法

  1. DynamicDataSourceContextHolder
public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>() {
    };


    /**
     * 数据源的 key集合,用于切换时判断数据源是否存在
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();

    /**
     * 切换数据源
     * @param key
     */
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    /**
     * 获取数据源
     * @return
     */
    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 重置数据源
     */
    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }

    /**
     * 判断是否包含数据源
     * @param key 数据源key
     * @return
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    /**
     * 添加数据源keys
     * @param keys
     * @return
     */
    public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
        return dataSourceKeys.addAll(keys);
    }
}

此处主要是创建用于存储数据源的threadlocal,包括设置,获取,及判断是否包含等方法

  1. DynamicDataSource
@Data
public class DynamicDataSource extends AbstractRoutingDataSource {


    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Nullable
    private static Map<String, DataSource> targetDataSources = new HashMap<>();

    @Nullable
    private DataSource defaultTargetDataSource;

    /**
     * 单例句柄
     */
    private static DynamicDataSource instance;

    private DynamicDataSource(){}

    private static final byte[] LOCK=new byte[0];

    public void put(String key,DataSource value){
        targetDataSources.put(key,value);
    }

    public void putAll(Map<String, DataSource> target){
        targetDataSources.putAll(target);
    }

    public DataSource get(String key){
        return targetDataSources.get(key);
    }

    public void setDefaultTargetDataSource(DataSource defaultTargetDataSource) {
        this.defaultTargetDataSource = defaultTargetDataSource;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return this.determineTargetDataSource().getConnection(username,password);
    }

    @Override
    protected DataSource determineTargetDataSource() {
        String lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = targetDataSources.get(lookupKey);
        if (dataSource == null && lookupKey == null) {
            dataSource = this.defaultTargetDataSource;
        }

        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
    }

    /**
     * 获取当前数据源
     * @return
     */
    @Override
    public String determineCurrentLookupKey() {
        logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }

    /**
     * 单例方法
     * @return
     */
    public static synchronized DynamicDataSource getInstance(){
        if(instance==null){
            synchronized (LOCK){
                if(instance==null){
                    instance=new DynamicDataSource();
                }
            }
        }
        return instance;
    }

    /**
     * 是否存在当前key的 DataSource
     * @param key
     * @return 存在返回 true, 不存在返回 false
     */
    public static boolean isExistDataSource(String key) {
        return targetDataSources.containsKey(key);
    }

    /**
     * 删除数据源
     * @param datasourceCode
     * @return
     */
    public boolean delDatasource(String datasourceCode) {
        assert targetDataSources != null;
        if (targetDataSources.containsKey(datasourceCode)) {
            DruidDataSource dataSource = (DruidDataSource) targetDataSources.get(datasourceCode);
            targetDataSources.remove(datasourceCode);
            dataSource.close();
            return true;
        } else {
            return false;
        }
    }
}

该类需要继承AbstractRoutingDataSource,定义targetDataSources集合和默认的数据源defaultTargetDataSource

determineTargetDataSource是比较重要的一个方法,用来决定用哪个数据源
a) determineCurrentLookupKey() 用来获取当前数据源的key值,其调用DynamicDataSourceContextHolder中的threadlocal来获取当前的key
b)从目标数据源集合中获取上述获取的key对应的value的数据源,若不存在,则设置为默认的
c)若目标数据源集合不存在且默认的数据源不存在,抛出异常,否则正常返回数据源

  1. SwichDb
@Component
public class SwitchDb {

    private static final Logger LOGGER = LoggerFactory.getLogger(SwitchDb.class);

    @Value("${spring.application.name}")
    private String applicationName;

    @Reference(registry = "dynamic")
    private DatasourceProvider datasourceProvider;

    public void changeDefault(){
        change("default");
    }

    /**
     * 切换数据源
     * @param dbKey key
     */
    public void change(String dbKey) {
        if(dbKey == null){
            return;
        }

        if(! DynamicDataSource.isExistDataSource(dbKey) ){
            creatDataSource(dbKey);
        }
        //获取当前连接的数据源对象的key
        String currentKey = DynamicDataSourceContextHolder.getDataSourceKey();
        if(currentKey == null){
            DynamicDataSourceContextHolder.setDataSourceKey(dbKey);
            return;
        }
        if(currentKey.equals(dbKey)){
            return;
        }
        LOGGER.info(String.format("切换到数据源%s成功",dbKey));
        System.out.println(String.format("切换到数据源%s成功",dbKey));
        DynamicDataSourceContextHolder.setDataSourceKey(dbKey);
    }


    /**
     * 创建数据源
     * @param dbKey key
     */
    private void creatDataSource(String dbKey){
        //实现创建数据库,数据库连接名称唯一
        AreaReq areaReq = new AreaReq();
        areaReq.setAreaCode(dbKey);
        areaReq.setApplicationName(applicationName);
        AreaResp datasourceConfigByAreaCode = datasourceProvider.getDatasourceByAreaCodeAndApplicationName(areaReq);
        DataSource dataSource = DatasourceUtil.parseToDataSource(datasourceConfigByAreaCode);
        Map<String,DataSource> map = new HashMap<>(2);
        map.put(dbKey, dataSource);
        DynamicDataSource.getInstance().putAll(map);
        System.out.println(String.format("创建数据源%s成功",areaReq.getAreaCode()));
    }
}

其中将连接信息转换生成数据源的方法如下:

public static DataSource parseToDataSource(AreaResp areaResp) {
        DruidDataSource bds = new DruidDataSource();
        bds.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        bds.setUrl(areaResp.getUrl());
        bds.setUsername(areaResp.getUsername());
        bds.setPassword(areaResp.getPassword());
        bds.setName(areaResp.getAreaCode());
        // 默认为false
        bds.setPoolPreparedStatements(true);
        // 超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收(默认为false,调整为true)
        bds.setRemoveAbandoned(true);
        // 超过时间限制,回收没有用(废弃)的连接(默认为 300秒)
        bds.setRemoveAbandonedTimeout(300);
        // 检查连接是否有效,每次间隔10分钟
        bds.setTestWhileIdle(true);
        bds.setTestOnBorrow(false);
        bds.setTestOnReturn(false);
        bds.setValidationQuery("select 1");
        bds.setValidationQueryTimeout(3);
        return bds;
    }

配置实际调用的类SwichDb类, 其中最重要的是change()方法和createDataSource()方法;

  1. change(): 用于通过key切换数据源,先判断数据源集合中是否已经初始化过本key对应的数据源,若未初始化过,则调用createDataSource()创建,之后判断当前的key是否已经是需要切换的key,若不是则将threadlocal设置为本key
  2. createDataSource(): 创建数据源的方法,此处是通过key查询数据库的连接信息,然后创建对应数据库类型的datasource,将其放入targetDataSouces,此处以sqlserver为例, 不同类型的数据库只需修改parseToDataSource()方法中的driverClassName和validationQuery即可
  1. 具体调用
switchDb.change(key);
  1. 补充:

若一个项目想要兼容多个种类的数据源的sql语句语法,可通过在mapper文件配置databaseId实现

  1. 配置一个configuration
 @Bean
 public DatabaseIdProvider databaseIdProvider() {
     VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
     Properties properties = new Properties();
     properties.setProperty("Oracle","oracle");
     properties.setProperty("MySQL","mysql");
     properties.setProperty("SQL Server","sqlserver");
     databaseIdProvider.setProperties(properties);
     return databaseIdProvider;
 }

  1. 在对应的mapper文件的标签上加上上述配置的的value,如下
<select id="getByName" databaseId="sqlserver" resultType="java.util.Map"></select>
<select id="getByName" databaseId="oracle" resultType="java.util.Map"></select>

以上是一些基于springboot的多数据源和动态数据源的实现,如果对你有帮助,记得留个赞~

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值