Spring 动态数据源教程


一、工程配置

1. 依赖导入

在工程中引入 jdbc-starterspring-data 依赖,内容如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
</dependency>

2. 文件配置

在工程 YML 配置文件中添加多个数据源信息,这里我以两个为例。

spring:
  datasource1:
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db1?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
  datasource2:
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://127.0.0.1:3306/test_db2?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

二、对象管理

1. 常量定义

为了提高程序的可维护性,这里将后续涉及的常量统一外置定义。

public class DatasourceConst {

    public static final String DS_MASTER = "datasource1";

    public static final String DS_SLAVE = "datasource2";
    
    public static final String TM_MASTER = "transactionManager1";

    public static final String TM_SLAVE = "transactionManager2";
}

2. Bean注入

通过 @Bean 将配置文件中的多个数据源信息注入 Spring 容器,其中 @Primary 标识数据源为默认源。

@Configuration
public class DatasourceConfig {
    @Bean(name = DatasourceConst.DS_MASTER)
    @ConfigurationProperties("spring.datasource1")
    public DataSource dataSource1() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = DatasourceConst.DS_SLAVE)
    @ConfigurationProperties("spring.datasource2")
    public DataSource dataSource2() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource() {
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put(DatasourceConst.DS_MASTER, dataSource1());
        dataSourceMap.put(DatasourceConst.DS_SLAVE, dataSource2());
        // 设置动态数据源
        DynamicDataSource dataSources = new DynamicDataSource();
        dataSources.setTargetDataSources(dataSourceMap);
        dataSources.setDefaultTargetDataSource(dataSource1());
        return dataSources;
    }
}

3. 事务配置

同理,当配置多个数据源时若需要使用事务需要将对应的事务管理注入 Spring 容器。

@Configuration
public class TransactionConfig {
    @Bean(name = DatasourceConst.TM_MASTER)
    @Autowired
    @Primary
    DataSourceTransactionManager transactionManager1(@Qualifier(DatasourceConst.DS_MASTER) DataSource datasource) {
        return new DataSourceTransactionManager(datasource);
    }

    @Bean(name = DatasourceConst.TM_SLAVE)
    @Autowired
    DataSourceTransactionManager transactionManager2(@Qualifier(DatasourceConst.DS_SLAVE) DataSource datasource) {
        return new DataSourceTransactionManager(datasource);
    }

    @Bean(name = DatasourceConst.TM_CHAIN)
    public ChainedTransactionManager chainedTransactionManager(@Qualifier(DatasourceConst.TM_MASTER) DataSourceTransactionManager tm1,
                                                               @Qualifier(DatasourceConst.TM_SLAVE) DataSourceTransactionManager tm2) {
        return new ChainedTransactionManager(tm1, tm2);
    }
}

三、数据切换

1. 管理工具

新建 DynamicDataSourceHolder 用于标识管理当前激活的数据源。

基于 Web 请求的特性,每个请求线程都可能使用到不同的数据源,因此选择 ThreadLocal 变量存储。

public class DynamicDataSourceHolder {

    /**
     * 动态数据源名称上下文
     */
    private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();

    /**
     * 获取数据源名称
     */
    public static String getContextKey() {
        String key = DATASOURCE_HOLDER.get();
        return key == null ? DatasourceConst.DS_MASTER : key;
    }

    /**
     * 设置/切换数据源
     */
    public static void setContextKey(String key) {
        DATASOURCE_HOLDER.set(key);
    }

    /**
     * 删除当前数据源名称
     */
    public static void remove() {
        DATASOURCE_HOLDER.remove();
    }
}

2. 切换配置

新建类继承于 AbstractRoutingDataSource,当程序在获取数据源连接池时将会执行 determineCurrentLookupKey() 方法。

public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * Through the key(bean name) to get which datasource to use.
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getContextKey();
    }
}

3. 演示示例

在应用中切换数据源时即可通过 DynamicDataSourceHolder 工具类快速切换,为了更直观显示效果,建议多个数据源选择不同的库执行查询并拼接。

@RestController
@RequestMapping("/api/sysUser")
public class SysUserController {

    @Resource
    private SysUserService sysUserService;

    @GetMapping("list")
    public ResponseEntity<Object> list() {
        List<SysUser> list1;
        try {
            // Query data with default datasource
            list1 = this.sysUserService.list();
            // Change datasource to slave node
            DynamicDataSourceHolder.setContextKey(DatasourceConst.DS_SLAVE);
            List<SysUser> list2 = this.sysUserService.list();
            // Merge data
            list1.addAll(list2);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            DynamicDataSourceHolder.remove();
        }
        return ResponseEntity.ok(list1);
    }
}

参考文章

  1. 搞定SpringBoot多数据源(2):动态数据源
  2. Spring Boot configure and use two data sources

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录