dynamic-datasource多数据源管理,程序运行期间动态添加数据源

栾昂雄
2023-12-01

版本

dynamic-datasource-spring-boot-starte v3.4.0

dynamic-datasource文档地址

解决未实现的功能

  1. 目前版本只支持单一位置加载数据源(只能从配置文件或者自定义加载数据源),不能多位置加载数据源
  2. 目前版本未实现动态添加数据源(在切换数据源时,不存在的数据源在数据库查询,添加进数据源连接池)

怎么集成dynamic多数据源在项目文档写的很清楚 文档地址

问题解决

  1. 自定义数据源加载类DatasourceConfig

import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.geniuses.common.constant.SmartFormConst;
import com.geniuses.datasource.DataSourceConstant;
import com.geniuses.datasource.web.entity.DataSourceEntity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;


/**
 * Description:  项目启动加载数据源<br>
 * Date: 2021-6-8 17:18<br>
 *
 * @author moon
 * @since 1.0.0
 */
@Configuration
public class DatasourceConfig {

	/**
     * 数据源自定义加载
     * 
     * @param properties 数据源配置(某些idea中properties字段可能会爆红,忽略即可,不影响使用 )
     * @return  DynamicDataSourceProvider
     */
    @Bean
    public DynamicDataSourceProvider dynamicDataSourceProvider(DynamicDataSourceProperties properties) {
        Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
        DataSourceProperty masterDataSourceProperty = datasourceMap.get("master");
        // 加载主数据源(yml中配置的数据源)
        return new AbstractJdbcDataSourceProvider(masterDataSourceProperty.getDriverClassName(), masterDataSourceProperty.getUrl(), masterDataSourceProperty.getUsername(), masterDataSourceProperty.getPassword()) {
            @Override
            protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
            // 该地方只能使用statement操作数据库(数据源未加载,不能注入service操作数据库)
            // 这里的数据源列表来源可以是任何地方
                ResultSet resultSet = statement.executeQuery("select pk_id,username,password,full_url from auth_data_source ");
                while (resultSet.next()){
                    String name = resultSet.getString("pk_id");
                    String username = resultSet.getString("username");
                    String password = DataSourceEntity.dataSourcePassDecrypt(resultSet.getString("password"));
                    String url = resultSet.getString("full_url");
                    String driver = DataSourceConstant.JDBC_DRIVER;

                    DataSourceProperty property = new DataSourceProperty();
                    property.setUsername(username);
                    property.setPassword(password);
                    property.setUrl(url);
                    property.setDriverClassName(driver);
                    datasourceMap.put(name, property);
                }
                return datasourceMap;
            }
        };
    }
}

该方法有个弊端: 如果有一个数据源连接失败,项目启动失败

  1. 扩展DsProcessor实现数据源懒加载(用到的时候去数据库查询,添加进数据源)

该方法可避免第一种方案的数据源连接失败导致的项目启动失败问题

  • 自定义MyDsProcessor继承DsSpelExpressionProcessor

import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
import com.geniuses.datasource.DataSourceConstant;
import com.geniuses.datasource.web.service.IDataSourceOnlineService;
import com.geniuses.datasource.web.service.IDataSourceService;
import lombok.Data;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;

/**
 * Description: 自定义DsProcessor <br>
 * Date: 2021-6-11 10:08<br>
 *
 * @author moon
 * @since 1.0.0
 */
@Data
public class MyDsProcessor extends DsSpelExpressionProcessor {

	/**
	* 查询数据源连接池信息的接口,根据文档中controller改造
	*/
    @Autowired
    private IDataSourceOnlineService dataSourceOnlineService;
	/**
	* 项目中存储数据源信息的service
	*/
    @Autowired
    private IDataSourceService dataSourceService;
	
	/**
	* @DS 注解中spel表达式的前缀,该值可自定义,想要使用该processor注解中的前缀就要和该值相同
	*/
    private static final String HEADER_PREFIX = "#my.";

    @Override
    public boolean matches(String key) {
        return key.startsWith(HEADER_PREFIX);
    }

    @Override
    public String doDetermineDatasource(MethodInvocation invocation, String key) {
        String name = super.doDetermineDatasource(invocation, key.replace("my.",""));
        if (name != null && !StrUtil.equals(name, DataSourceConstant.PRIMARY_DATA_SOURCE_NAME, true)) {
            Set<String> nameSet = dataSourceOnlineService.onlineDataSource();
            if (!nameSet.contains(name)) {
                synchronized (HEADER_PREFIX) {
                    // 加锁防止重复添加同一个数据源
                    nameSet = dataSourceOnlineService.onlineDataSource();
                    if (!nameSet.contains(name)) {
                        // 添加该数据源
                        dataSourceOnlineService.add(dataSourceService.getById(name),true);
                    }
                }
            }

        }
        return name;
    }
}
  • DataSourceOnlineServiceImpl主要方法
@Service
public class DataSourceOnlineServiceImpl implements IDataSourceOnlineService {

    @Autowired
    private DataSource dataSource;

    @Autowired
    @SuppressWarnings("all")
    private DefaultDataSourceCreator dataSourceCreator;
    
    @Override
    public Set<String> add(DataSourceEntity entity, boolean passEncrypted) {

        DataSourceProperty dataSourceProperty = this.getDataSourceProperty(entity, passEncrypted);

        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
        ds.addDataSource(entity.getId(), dataSource);
        return ds.getCurrentDataSources().keySet();
    }


    private DataSourceProperty getDataSourceProperty(DataSourceEntity entity, boolean passEncrypted) {
        DataSourceProperty dataSourceProperty = new DataSourceProperty();
        dataSourceProperty.setDriverClassName(DatasourceTypeEnum.getDriverClassName(entity.getDatabaseType()));
        dataSourceProperty.setUsername(entity.getUsername());
        if (passEncrypted) {
        	// 解密
			dataSourceProperty.setPassword(DataSourceEntity.dataSourcePassDecrypt(entity.getUsrpwd()));
        } else {
            dataSourceProperty.setPassword(entity.getUsrpwd());
        }
        dataSourceProperty.setUrl(entity.getFullUrl());
        dataSourceProperty.setPoolName(entity.getId());
        return dataSourceProperty;
    }
}
  • 使用
    在需要使用上述方法定义数据源的地方加上注解即可
	@DS("#my.dataSource")
    @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW)
    public void dsTest(){}
 类似资料: