【Java】Spring Boot配置动态数据源

SpringBoot配置动态数据源

一、动态多数据源的配置

1.1 创建动态数据源

通过实现Spring提供的AbstractRoutingDataSource类,可以实现自己的数据源选择逻辑,从而可以实现数据源的动态切换。

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Value("${spring.datasource.default-db-key}")
    private String defaultDbKey;·

    @Override
    protected Object determineCurrentLookupKey() {
        String currentDb = DynamicDataSourceService.currentDb();
        if (currentDb == null) {
            return defaultDbKey;
        }
        return currentDb;
    }
}

1.2 创建动态数据源配置类

跟配置静态多数据源一样,需要手动配置下面的三个 Bean,只不过DynamicDataSource类的targetDataSources是空的。

@Configuration
public class DynamicDataSourceConfig {
    /**
     * 动态数据源
     */
    @Bean
    public DynamicDataSource dynamicDataSource() {
        DynamicDataSource dataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        dataSource.setTargetDataSources(targetDataSources);
        return dataSource;
    }

    /**
     * 会话工厂
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        sqlSessionFactoryBean.setConfiguration(configuration);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/repository/*.xml"));
        return sqlSessionFactoryBean;
    }

    /**
     * 事务管理器
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
    
}

1.3 创建动态数据源服务类

这是一个比较核心的工具类,提供了一些静态方法从而可以实现一些功能,包括:
动态添加数据源切换数据源重置数据源获取数据源
在 DynamicDataSource 类中,就是调用了 DynamicDataSourceService 类的 switchDb
方法实现的数据源选择。
通过查看下面的代码就能看出来使用线程本地的技术实现的多个请求数据源互不相干。

@Slf4j
public class DynamicDataSourceService {
    private static final Map<Object, Object> dataSources = new HashMap<>();
    private static final ThreadLocal<String> dbKeys = ThreadLocal.withInitial(() -> null);

    /**
     * 动态添加一个数据源
     *
     * @param name       数据源的key
     * @param dataSource 数据源对象
     */
    public static void addDataSource(String name, DataSource dataSource) {
        DynamicDataSource dynamicDataSource = App.context.getBean(DynamicDataSource.class);
        dataSources.put(name, dataSource);
        dynamicDataSource.setTargetDataSources(dataSources);
        dynamicDataSource.afterPropertiesSet();

        log.info("addDataSource 添加了数据源:{}", name);
    }

    /**
     * 切换数据源
     */
    public static void switchDb(String dbKey) {
        dbKeys.set(dbKey);
        log.info("switchDb 切换数据源:{}", dbKey);
    }

    /**
     * 重置数据源
     */
    public static void resetDb() {
        dbKeys.remove();
        log.info("resetDb 重置数据源");
    }

    /**
     * 获取当前数据源
     */
    public static String currentDb() {
    	log.info("currentDb 获取当前数据源");
        return dbKeys.get();
    }
}

至此,动态多数据源的配置就完成了,只需要编写数据源生成的逻辑,在程序运行时调用 addDataSource 方法即可将数据源动态添加到上下文中,并支持动态切换。

下面简单介绍一下基于配置文件的数据源生成。

二、数据源生成器

2.1 自定义了一个数据源生成器接口用于定义动态生成数据源的要求。

public interface DataSourceProvider {
    List<DataSource> provide();
}

2.2 然后编写了一个根据配置文件提供数据源并配置的实现类。

@Component
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public class YmlDataSourceProvider implements DataSourceProvider {

    private List<Map<String, DataSourceProperties>> dataSources;

    private DataSource buildDataSource(DataSourceProperties prop) {
        DataSourceBuilder<?> builder = DataSourceBuilder.create();
        builder.driverClassName(prop.getDriverClassName());
        builder.username(prop.getUsername());
        builder.password(prop.getPassword());
        builder.url(prop.getJdbcUrl());
        return builder.build();
    }

    @Override
    public List<DataSource> provide() {
        List<DataSource> res = new ArrayList<>();
        dataSources.forEach(map -> {
            Set<String> keys = map.keySet();
            keys.forEach(key -> {
                DataSourceProperties properties = map.get(key);
                DataSource dataSource = buildDataSource(properties);
                DynamicDataSourceService.addDataSource(key, dataSource);
            });
        });
        return res;
    }

    @PostConstruct
    public void init() {
        provide();
    }

    public List<Map<String, DataSourceProperties>> getDataSources() {
        return dataSources;
    }

    public void setDataSources(List<Map<String, DataSourceProperties>> dataSources) {
        this.dataSources = dataSources;
    }
}

看一下对应的配置文件内容:

spring:
  datasource:
    default-db-key: db0
    hikari:
      data-sources:
        - db0:
            jdbc-url: jdbc:mysql://127.0.0.1:3306/codingcea
            username: codingce
            password: 1234567890
            driver-class-name: com.mysql.cj.jdbc.Driver
        - db1:
            jdbc-url: jdbc:mysql://127.0.0.1:3306/codingceb
            username: codingce
            password: 1234567890
            driver-class-name: com.mysql.cj.jdbc.Driver

这样就实现了应用启动时自动将配置文件中的数据源配置读取并生成数据源注册到上下文中;
当然也可以有其他的实现,比如从数据库读取并配置,或者通过接口请求的方式生成都可以,只要实现自己的DataSourceProvider就可以。