Backend Development 14 min read

Implementing Dynamic Data Source Switching in Spring Boot with ThreadLocal and AbstractRoutingDataSource

This article demonstrates how to build a dynamic data source switching mechanism in Spring Boot by leveraging ThreadLocal for thread‑scoped context and AbstractRoutingDataSource for routing, covering manual implementation, annotation‑driven switching, and runtime addition of new data sources.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Dynamic Data Source Switching in Spring Boot with ThreadLocal and AbstractRoutingDataSource

When a business requirement involves reading from multiple databases and writing into a single one, dynamic data source switching becomes essential. The article first explains why the built‑in dynamic-datasource-spring-boot-starter may not be usable and proposes a custom solution based on ThreadLocal and AbstractRoutingDataSource .

1. Introduction

ThreadLocal provides a separate variable instance per thread, eliminating concurrency issues, while AbstractRoutingDataSource determines the current lookup key via determineCurrentLookupKey() to select the appropriate datasource.

2. Code Implementation

2.1 ThreadLocal Helper

public class DataSourceContextHolder {
    private static final ThreadLocal
DATASOURCE_HOLDER = new ThreadLocal<>();
    public static void setDataSource(String dataSourceName) { DATASOURCE_HOLDER.set(dataSourceName); }
    public static String getDataSource() { return DATASOURCE_HOLDER.get(); }
    public static void removeDataSource() { DATASOURCE_HOLDER.remove(); }
}

2.2 AbstractRoutingDataSource Extension

public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(DataSource defaultDataSource, Map
targetDataSources) {
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

2.3 Configuration (application.yml)

spring:
  datasource:
    druid:
      master:
        url: jdbc:mysql://xxxxxx:3306/test1?... 
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      slave:
        url: jdbc:mysql://xxxxx:3306/test2?... 
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      ...

2.4 Bean Registration

@Configuration
public class DateSourceConfig {
    @Bean @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); }
    @Bean @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); }
    @Bean(name = "dynamicDataSource") @Primary
    public DynamicDataSource createDynamicDataSource() {
        Map
map = new HashMap<>();
        DataSource defaultDs = masterDataSource();
        map.put("master", defaultDs);
        map.put("slave", slaveDataSource());
        return new DynamicDataSource(defaultDs, map);
    }
}

2.5 Service Usage

@GetMapping("/getData.do/{datasourceName}")
public String getMasterData(@PathVariable("datasourceName") String datasourceName) {
    DataSourceContextHolder.setDataSource(datasourceName);
    TestUser user = testUserMapper.selectOne(null);
    DataSourceContextHolder.removeDataSource();
    return user.getUserName();
}

The above method shows how passing different datasource names (e.g., master or slave ) yields different query results, confirming the dynamic switch.

2.5 Optimization

2.5.1 Annotation‑Based Switching

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented @Inherited
public @interface DS { String value() default "master"; }

Using an AOP aspect, the datasource is set before method execution and cleared afterwards:

@Aspect @Component @Slf4j
public class DSAspect {
    @Pointcut("@annotation(com.jiashn.dynamic_datasource.dynamic.aop.DS)")
    public void dynamicDataSource() {}
    @Around("dynamicDataSource()")
    public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        DS ds = method.getAnnotation(DS.class);
        if (Objects.nonNull(ds)) { DataSourceContextHolder.setDataSource(ds.value()); }
        try { return point.proceed(); } finally { DataSourceContextHolder.removeDataSource(); }
    }
}

Controller methods can now be annotated with @DS("slave") to automatically switch the datasource.

2.5.2 Dynamic Addition of Data Sources

A DataSourceEntity class stores connection details and a unique key. The enhanced DynamicDataSource keeps a mutable targetDataSourceMap and provides createDataSource(List ) to add new datasources at runtime, validating connections via DriverManager and configuring Druid pools.

public void createDataSource(List
dataSources) {
    for (DataSourceEntity ds : dataSources) {
        Class.forName(ds.getDriverClassName());
        DriverManager.getConnection(ds.getUrl(), ds.getUserName(), ds.getPassWord());
        DruidDataSource dataSource = new DruidDataSource();
        BeanUtils.copyProperties(ds, dataSource);
        dataSource.setTestOnBorrow(true);
        dataSource.setTestWhileIdle(true);
        dataSource.setValidationQuery("select 1");
        dataSource.init();
        targetDataSourceMap.put(ds.getKey(), dataSource);
    }
    super.setTargetDataSources(targetDataSourceMap);
    super.afterPropertiesSet();
}

A CommandLineRunner reads datasource definitions from a table ( test_db_info ) at application startup and invokes dynamicDataSource.createDataSource(dsList) , making the new connections immediately available for the existing switching logic.

3. Summary

The tutorial walks through building a complete dynamic datasource solution from scratch, covering the core concepts of thread‑local context, routing datasource, annotation‑driven switching, and runtime datasource registration, providing a lightweight alternative to third‑party starters while illustrating practical patterns for multi‑tenant or read/write‑split architectures.

JavadatabaseSpringBootthreadlocalMyBatisPlusAbstractRoutingDataSourceDynamicDataSource
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.