Backend Development 15 min read

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

This article explains how to build a custom dynamic data‑source solution for Spring Boot by leveraging ThreadLocal and AbstractRoutingDataSource, provides complete Java code for context holders, routing datasource, configuration, annotation‑driven switching, and runtime addition of new data sources.

IT Xianyu
IT Xianyu
IT Xianyu
Implementing Dynamic Data Source Switching in Spring Boot Using ThreadLocal and AbstractRoutingDataSource

The author needed to fetch data from multiple databases within a single business request and therefore implemented a custom dynamic data‑source mechanism because the existing dynamic-datasource-spring-boot-starter could not be used.

1. Introduction

ThreadLocal provides a per‑thread variable to avoid data inconsistency in concurrent environments, while AbstractRoutingDataSource selects the current datasource based on a lookup key before each query.

2. Code Implementation

2.1 ThreadLocal holder

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 11:21
 */
public class DataSourceContextHolder {
    // Thread‑local variable storing the datasource name
    private static final ThreadLocal
DATASOURCE_HOLDER = new ThreadLocal<>();

    /**
     * Set datasource name
     */
    public static void setDataSource(String dataSourceName) {
        DATASOURCE_HOLDER.set(dataSourceName);
    }

    /**
     * Get current thread's datasource name
     */
    public static String getDataSource() {
        return DATASOURCE_HOLDER.get();
    }

    /**
     * Remove the datasource from the current thread
     */
    public static void removeDataSource() {
        DATASOURCE_HOLDER.remove();
    }
}

2.2 AbstractRoutingDataSource subclass

/**
 * @author: jiangjs
 * @description: Dynamic datasource routing implementation
 * @date: 2023/7/27 11:18
 */
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 Spring configuration (application.yml and @Configuration class)

# application.yml snippet
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    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
      ...
@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
dataSourceMap = new HashMap<>();
        DataSource defaultDataSource = masterDataSource();
        dataSourceMap.put("master", defaultDataSource);
        dataSourceMap.put("slave", slaveDataSource());
        return new DynamicDataSource(defaultDataSource, dataSourceMap);
    }
}

2.4 Controller usage

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

The above endpoint demonstrates manual switching by setting the datasource name in ThreadLocal before the query.

2.5 Annotation‑driven switching

Define a custom @DS annotation and an AOP aspect that sets and clears the datasource automatically.

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DS {
    String value() default "master";
}
@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 {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.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 use @DS("slave") to switch to the slave datasource without manual code.

2.6 Dynamic addition of new datasources at runtime

A DataSourceEntity class stores connection parameters, and DynamicDataSource is extended with a createDataSource(List ) method that builds a DruidDataSource for each entry, validates the connection, and updates the internal map.

@Data
@Accessors(chain = true)
public class DataSourceEntity {
    private String url;
    private String userName;
    private String passWord;
    private String driverClassName;
    private String key;
}
public void createDataSource(List
dataSources) {
    try {
        if (CollectionUtils.isNotEmpty(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();
                this.targetDataSourceMap.put(ds.getKey(), dataSource);
            }
            super.setTargetDataSources(this.targetDataSourceMap);
            super.afterPropertiesSet();
        }
    } catch (ClassNotFoundException | SQLException e) {
        log.error("---程序报错---:{}", e.getMessage());
    }
}

A CommandLineRunner component reads datasource definitions from a table ( test_db_info ) at application startup and calls dynamicDataSource.createDataSource(...) to register them.

@Component
public class LoadDataSourceRunner implements CommandLineRunner {
    @Resource
    private DynamicDataSource dynamicDataSource;
    @Resource
    private TestDbInfoMapper testDbInfoMapper;

    @Override
    public void run(String... args) throws Exception {
        List
testDbInfos = testDbInfoMapper.selectList(null);
        if (CollectionUtils.isNotEmpty(testDbInfos)) {
            List
ds = new ArrayList<>();
            for (TestDbInfo testDbInfo : testDbInfos) {
                DataSourceEntity sourceEntity = new DataSourceEntity();
                BeanUtils.copyProperties(testDbInfo, sourceEntity);
                sourceEntity.setKey(testDbInfo.getName());
                ds.add(sourceEntity);
            }
            dynamicDataSource.createDataSource(ds);
        }
    }
}

After the application starts, the newly added datasources can be accessed via the same @DS annotation or the manual DataSourceContextHolder.setDataSource(...) approach, as demonstrated by the test results shown in the article.

Overall, the article provides a complete, production‑ready guide for implementing, testing, and extending dynamic datasource switching in a Spring Boot backend project.

backendJavaSpringBootMyBatis-PlusthreadlocalAbstractRoutingDataSourceDynamic DataSource
IT Xianyu
Written by

IT Xianyu

We share common IT technologies (Java, Web, SQL, etc.) and practical applications of emerging software development techniques. New articles are posted daily. Follow IT Xianyu to stay ahead in tech. The IT Xianyu series is being regularly updated.

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.