Implementing Dynamic Data Source Switching in Spring Boot with ThreadLocal and AbstractRoutingDataSource
This article demonstrates how to implement dynamic data source switching in a Spring Boot application by leveraging ThreadLocal and AbstractRoutingDataSource, providing step‑by‑step code examples for context holders, custom routing, annotation‑driven switching, and runtime addition of new data sources.
When a business requirement involves fetching data from multiple databases and writing it into a current database, dynamic data source switching becomes necessary. The author initially tried the dynamic-datasource-spring-boot-starter but faced environment issues, so they implemented their own solution using ThreadLocal and AbstractRoutingDataSource .
1. Introduction
ThreadLocal provides a thread‑local variable, ensuring each thread accesses its own copy of a variable, which isolates data and reduces synchronization overhead. AbstractRoutingDataSource selects the current data source based on a user‑defined lookup key via the determineCurrentLookupKey() method.
2. Code Implementation
2.1 ThreadLocal Context Holder
/**
* @author: jiangjs
* @description:
* @date: 2023/7/27 11:21
*/
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 Custom AbstractRoutingDataSource
/**
* @author: jiangjs
* @description: Implement dynamic data source routing
* @date: 2023/7/27 11:18
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSources){
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(targetDataSources);
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}2.3 Configuration (application.yml and Java Config)
# application.yml snippet
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
url: jdbc:mysql://xxxxxx:3306/test1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
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<Object,Object> dataSourceMap = new HashMap<>();
DataSource defaultDataSource = masterDataSource();
dataSourceMap.put("master", defaultDataSource);
dataSourceMap.put("slave", slaveDataSource());
return new DynamicDataSource(defaultDataSource, dataSourceMap);
}
}2.4 Testing Basic Switching
@GetMapping("/getData.do/{datasourceName}")
public String getMasterData(@PathVariable("datasourceName") String datasourceName){
DataSourceContextHolder.setDataSource(datasourceName);
TestUser testUser = testUserMapper.selectOne(null);
DataSourceContextHolder.removeDataSource();
return testUser.getUserName();
}Running the endpoint with master or slave returns data from the corresponding database, confirming the dynamic routing works.
2.5 Annotation‑Driven Switching
Define a custom annotation @DS and an AOP aspect to set and clear the data source 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 be annotated with @DS("slave") to switch to the slave data source without manual code.
2.6 Runtime Addition of New Data Sources
A DataSourceEntity class stores connection details, and DynamicDataSource gains a createDataSource(List ) method that validates, builds, and registers new data sources into the internal map.
public void createDataSource(List
dataSources){
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();
}
}A CommandLineRunner implementation reads a test_db_info table at startup, converts each row to a DataSourceEntity , and calls dynamicDataSource.createDataSource(ds) , making the new sources immediately usable.
Subsequent API calls with the newly added source name retrieve data correctly, proving that data sources can be added dynamically at runtime.
Overall, the tutorial provides a complete guide—from basic ThreadLocal usage to annotation‑driven switching and dynamic addition—enabling developers to manage multiple databases efficiently within a Spring Boot application.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.