Master Dynamic Data Source Switching in SpringBoot: A Step‑by‑Step Guide

This article walks through the complete process of implementing dynamic data source switching in a SpringBoot application, covering the core concepts of thread‑local context, abstract routing, custom annotations with AOP, configuration, code examples, and important considerations such as transaction management and performance.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Master Dynamic Data Source Switching in SpringBoot: A Step‑by‑Step Guide

Dynamic Data Source Switching in Spring Boot

Concept

Thread isolation : Use ThreadLocal to store the identifier of the data source for the current thread.

Routing : Extend AbstractRoutingDataSource and override determineCurrentLookupKey() to return the identifier from the context holder.

Annotation + AOP : Define a custom annotation (e.g., @TargetDataSource) and an aspect that sets the identifier before method execution and clears it afterwards.

Required Maven Dependencies

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

Configuration of Multiple Data Sources (application.yml)

spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/db1
      username: root
      password: 123456
    secondary:
      url: jdbc:mysql://localhost:3306/db2
      username: root
      password: 123456

Context Holder

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();

    public static void setDataSourceKey(String key) {
        CONTEXT.set(key);
    }

    public static String getDataSourceKey() {
        return CONTEXT.get();
    }

    public static void clear() {
        CONTEXT.remove();
    }
}

Routing Data Source

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

DataSource Configuration

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource() {
        DynamicDataSource ds = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("primary", primaryDataSource());
        targetDataSources.put("secondary", secondaryDataSource());
        ds.setTargetDataSources(targetDataSources);
        ds.setDefaultTargetDataSource(primaryDataSource());
        return ds;
    }
}

Custom Annotation

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    /** Identifier of the data source, defaults to "primary". */
    String value() default "primary";
}

Aspect for Automatic Switching

@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(targetDataSource)")
    public void switchDataSource(JoinPoint jp, TargetDataSource targetDataSource) {
        DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value());
    }

    @After("@annotation(targetDataSource)")
    public void restoreDataSource(JoinPoint jp, TargetDataSource targetDataSource) {
        DynamicDataSourceContextHolder.clear();
    }
}

Usage Example

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @TargetDataSource("primary")
    public List<User> getPrimaryUsers() {
        return jdbcTemplate.query("SELECT * FROM users", new UserRowMapper());
    }

    @TargetDataSource("secondary")
    public List<User> getSecondaryUsers() {
        return jdbcTemplate.query("SELECT * FROM users", new UserRowMapper());
    }
}

Important Considerations

Transaction management : Spring’s single‑phase transaction manager cannot span multiple physical data sources. Use a distributed transaction solution (e.g., Seata, Atomikos) if a single logical transaction must cover both.

Connection‑pool isolation : Each physical data source should have its own pool configuration (max size, timeout, etc.) to avoid contention.

Performance impact : Switching the data source incurs a ThreadLocal write and a lookup in the routing map. Group operations that use the same data source to reduce the number of switches.

Testing : Verify the selected datasource by logging the value returned from DynamicDataSourceContextHolder.getDataSourceKey() inside the aspect.

Reference Implementation

The complete source code is hosted on GitHub at https://github.com/your-repo/dynamic-datasource-demo. Clone with

git clone https://github.com/your-repo/dynamic-datasource-demo.git

.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaaopdatabasemulti-tenantSpringBootThreadLocaldynamic-datasource
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

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.