Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Dynamic Data Source Routing with Spring Boot

Tech 2

Add Required Dependencies

Add the following dependencies to your project's pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

Configure Multiple Data Sources

Define your data source connections in application.yml:

spring:
  datasource:
    main:
      url: jdbc:mysql://localhost:3306/main_db
      username: root
      password: your_db_password
      driver-class-name: com.mysql.cj.jdbc.Driver
    replica:
      url: jdbc:mysql://localhost:3306/replica_db
      username: root
      password: your_db_password
      driver-class-name: com.mysql.cj.jdbc.Driver

Define Data Source Configuration Class

Create a configuration class to register individual data source beans:

package com.example.dynamicds.config;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class MultiDataSourceConfig {

    @Primary
    @Bean(name = "mainDataSource")
    @ConfigurationProperties("spring.datasource.main")
    public HikariDataSource mainDataSource() {
        return new HikariDataSource();
    }

    @Bean(name = "replicaDataSource")
    @ConfigurationProperties("spring.datasource.replica")
    public HikariDataSource replicaDataSource() {
        return new HikariDataSource();
    }
}

Implemant Data Source Context Holder

Create a thread-local storage class to track the data source associated with the current request thread:

package com.example.dynamicds.routing;

public class DataSourceContext {
    private static final ThreadLocal<String> CURRENT_DATA_SOURCE = new ThreadLocal<>();

    public static void setActiveDataSource(String dataSourceKey) {
        CURRENT_DATA_SOURCE.set(dataSourceKey);
    }

    public static String getActiveDataSource() {
        return CURRENT_DATA_SOURCE.get();
    }

    public static void clearActiveDataSource() {
        CURRENT_DATA_SOURCE.remove();
    }
}

Create Dynamic Routing Data Source

Extend Spring's AbstractRoutingDataSource to implement dynamic lookup of active data sources:

package com.example.dynamicds.routing;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContext.getActiveDataSource();
    }
}

Register Dynamic Data Source Bean

Create a configuration class to wrap the individual data sources into the dynamic routing implementation:

package com.example.dynamicds.config;

import com.example.dynamicds.routing.DynamicDataSourceRouter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DynamicDataSourceConfig {

    @Bean
    public DynamicDataSourceRouter dynamicDataSource(
            @Qualifier("mainDataSource") DataSource mainDataSource,
            @Qualifier("replicaDataSource") DataSource replicaDataSource) {
        DynamicDataSourceRouter router = new DynamicDataSourceRouter();
        Map<Object, Object> dataSourceRegistry = new HashMap<>();
        dataSourceRegistry.put("main", mainDataSource);
        dataSourceRegistry.put("replica", replicaDataSource);
        router.setTargetDataSources(dataSourceRegistry);
        router.setDefaultTargetDataSource(mainDataSource);
        return router;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DynamicDataSourceRouter dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}

Define Routing Annotation

Create a custom annotation to mark methods or classes that need to use a specific data source:

package com.example.dynamicds.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDataSource {
    String value();
}

Implement Routing Aspect

Create an AOP aspect to intercept annotated methods and switch the active data source before execution:

package com.example.dynamicds.aspect;

import com.example.dynamicds.annotation.UseDataSource;
import com.example.dynamicds.routing.DataSourceContext;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(-10)
@Component
public class DataSourceRoutingAspect {

    @Before("@annotation(useDataSource)")
    public void switchDataSourceBeforeExecution(UseDataSource useDataSource) {
        DataSourceContext.setActiveDataSource(useDataSource.value());
    }

    @After("@annotation(useDataSource)")
    public void clearDataSourceAfterExecution(UseDataSource useDataSource) {
        DataSourceContext.clearActiveDataSource();
    }
}

Use Dynamic Data Source in Business Logic

Add the @UseDataSource annotation to methods that require a specific data source:

package com.example.dynamicds.service;

import com.example.dynamicds.annotation.UseDataSource;
import com.example.dynamicds.entity.Customer;
import com.example.dynamicds.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CustomerService {
    @Autowired
    private CustomerRepository customerRepository;

    @UseDataSource("main")
    public List<Customer> fetchAllCustomersFromMain() {
        return customerRepository.findAll();
    }

    @UseDataSource("replica")
    public List<Customer> fetchAllCustomersFromReplica() {
        return customerRepository.findAll();
    }
}

Verify Dynamic Routing Functionality

Write a test case to confirm data source switching works as expected:

package com.example.dynamicds;

import com.example.dynamicds.entity.Customer;
import com.example.dynamicds.service.CustomerService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;

@SpringBootTest
public class DynamicDataSourceRoutingTest {

    @Autowired
    private CustomerService customerService;

    @Test
    public void testDataSourceSwitching() {
        List<Customer> mainCustomers = customerService.fetchAllCustomersFromMain();
        System.out.println("Main datasource customer count: " + mainCustomers.size());
        List<Customer> replicaCustomers = customerService.fetchAllCustomersFromReplica();
        System.out.println("Replica datasource customer count: " + replicaCustomers.size());
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.