Open In App

Spring – AbstractRoutingDataSource

Last Updated : 15 Nov, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we’ll look at Spring’s AbstractRoutingDatasource as an abstract DataSource implementation that dynamically determines the actual DataSource based on the current context. Now this question will come to our mind when we may need it i.e. when we should go for AbstractRoutingDatasource implementation. Sometimes we have a requirement to switch databases dynamically on the basis of branch/region/locale or any other parameter and perform the operation based on the request. So let’s understand the implementation of the AbstractRoutingDatasource by taking an example. Suppose, we have two branches of GeeksForGeeks i.e. Noida and Bangalore, and one database for each branch. We need to get details from the Noida database if the request comes from the Noida branch and if the request comes from the Bangalore branch then from the Bangalore database.

In this tutorial, we will expose a REST endpoint that connects with either noidadb or bangaloredb and fetch the interns information from the table based on the request, and return the JSON objects. Here we will design an endpoint: http://localhost:8080/interns and we will implement the AbstractRoutingDatasource in the application. When we will hit the endpoint http://localhost:8080/interns from branch Bangalore then we will get the below response which is nothing but the records from the bangaloredb database.

[
   {
       "id": 100,
       "name": "Bishal Dubey",
       "address": "Dibrugarh, Assam",
       "dob": "11-11-1994",
       "branch": "bangalore"
   },
   {
       "id": 101,
       "name": "Bikash Dubey",
       "address": "Dibrugarh, Assam",
       "dob": "07-03-1997",
       "branch": "bangalore"
   },
   {
       "id": 102,
       "name": "Nisha Ojha",
       "address": "Makum, Assam",
       "dob": "31-08-1997",
       "branch": "bangalore"
   }
]

When we will hit the endpoint http://localhost:8080/interns from branch Noida then we will get the below response which is nothing but the records from the noidadb database.

[
   {
       "id": 1,
       "name": "Neha Dubey",
       "address": "Doomdooma, Assam",
       "dob": "31-08-1992",
       "branch": "noida"
   },
   {
       "id": 2,
       "name": "Roshan Ojha",
       "address": "Makum, Assam",
       "dob": "22-10-1998",
       "branch": "noida"
   },
   {
       "id": 3,
       "name": "Muskan Pandey",
       "address": "Margherita, Assam",
       "dob": "12-05-2000",
       "branch": "noida"
   }
]

Note: Whenever we will hit the endpoint then we will pass one extra parameter in the request header named “branch” with the value Noida/Bangalore.

Now let’s jump to the implementation of AbstractRoutingDatasource, we will proceed step by step.

Step by Step Implementation

Project Structure:

Project structure

 

Create a maven project with the required dependencies in the pom.xml file

XML




<?xml version="1.0" encoding="UTF-8"?>
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath />
    </parent>
    <groupId>com.gfg.scripter</groupId>
    <artifactId>scripter-datasource-routing</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
  
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
  
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>


Configure the database sources in the application.properties for both noidadb as well as bangaloredb with a few other configurations.

# database details for noida branch
noida.datasource.url=jdbc:mysql://localhost:3306/noidadb?createDatabaseIfNotExist=true
noida.datasource.username=root
noida.datasource.password=root

# database details for bangalore branch
bangalore.datasource.url=jdbc:mysql://localhost:3306/bangaloredb?createDatabaseIfNotExist=true
bangalore.datasource.username=root
bangalore.datasource.password=root

# JPA property settings
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true

Create an Intern entity that will be used to refer an Intern in both databases.

Java




package com.gfg.scripter.entity;
  
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
  
@Entity
@Table(name = "intern")
public class Intern {
  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String address;
    private String dob;
    private String branch;
    // Generate Getters and Setters
}


Create DataSource Context i.e. It is nothing but enum/constant which contains the name of both gfg branches. Here AbstractRoutingDataSource needs a piece of information to decide which database to route to and here this datasource context will help.

Java




package com.gfg.scripter.constant;
  
public enum BranchEnum {
    NOIDA, BANGALORE
}


Now we will create Context Holder which will hold the current context as ThreadLocal reference.

Java




package com.gfg.scripter.config;
  
import com.gfg.scripter.constant.BranchEnum;
  
public class BranchContextHolder {
  
    private static ThreadLocal<BranchEnum> threadLocal = new ThreadLocal<>();
  
    public static void setBranchContext(BranchEnum branchEnum) {
        threadLocal.set(branchEnum);
    }
  
    public static BranchEnum getBranchContext() {
        return threadLocal.get();
    }
  
    public static void clearBranchContext() {
        threadLocal.remove();
    }
}


Create a DataSource Routing class which will extend the AbstractRoutingDatasource class and it will contain the map of real data sources.

Java




package com.gfg.scripter.config;
  
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import com.gfg.scripter.constant.BranchEnum;
  
public class DataSourceRouting extends AbstractRoutingDataSource {
  
    @Override
    protected Object determineCurrentLookupKey() {
        return BranchContextHolder.getBranchContext();
    }
  
    public void initDatasource(DataSource bangaloreDataSource,
            DataSource noidaDataSource) {
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(BranchEnum.NOIDA, noidaDataSource);
        dataSourceMap.put(BranchEnum.BANGALORE, bangaloreDataSource);
        this.setTargetDataSources(dataSourceMap);
          
          // Here we have to provide default datasource details.
        this.setDefaultTargetDataSource(noidaDataSource);
    }
}


Now we will create DataSource Configuration i.e. the object/bean of noida/bangalore databases and other required configurations i.e. transaction manager & entity manager that will point to the Intern entity and Intern repository.

Java




package com.gfg.scripter.config;
  
import javax.sql.DataSource;
  
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
  
import com.gfg.scripter.entity.Intern;
  
@Configuration
@EnableJpaRepositories(basePackages = "com.gfg.scripter.repository", transactionManagerRef = "transcationManager", entityManagerFactoryRef = "entityManager")
@EnableTransactionManagement
public class DataSourceConfig {
  
    @Bean
    @Primary
    @Autowired
    public DataSource dataSource() {
        DataSourceRouting routingDataSource = new DataSourceRouting();
        routingDataSource.initDatasource(noidaDataSource(), bangaloreDataSource());
        return routingDataSource;
    }
  
    @Bean
    @ConfigurationProperties(prefix = "noida.datasource")
    public DataSource noidaDataSource() {
        return DataSourceBuilder.create().build();
    }
  
    @Bean
    @ConfigurationProperties(prefix = "bangalore.datasource")
    public DataSource bangaloreDataSource() {
        return DataSourceBuilder.create().build();
    }
  
    @Bean(name = "entityManager")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dataSource()).packages(Intern.class).build();
    }
  
    @Bean(name = "transcationManager")
    public JpaTransactionManager transactionManager(
            @Autowired @Qualifier("entityManager") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
        return new JpaTransactionManager(entityManagerFactoryBean.getObject());
    }
}


Now we will create another class i.e. DataSource Interceptor which will intercept every request and fetch the branch information from the request header and place it to the context holder that we already created i.e. BranchContextHolder which will switch data source. After that, we will register this interceptor to WebMvcConfigurer.

Java




package com.gfg.scripter.config;
  
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.gfg.scripter.constant.BranchEnum;
  
@Component
public class DataSourceInterceptor extends HandlerInterceptorAdapter {
  
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
  
        String branch = request.getHeader("branch");
        if (BranchEnum.BANGALORE.toString().equalsIgnoreCase(branch))
            BranchContextHolder.setBranchContext(BranchEnum.BANGALORE);
        else
            BranchContextHolder.setBranchContext(BranchEnum.NOIDA);
        return super.preHandle(request, response, handler);
    }
}


Java




package com.gfg.scripter.config;
  
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  
@Configuration
public class WebConfig implements WebMvcConfigurer {
  
    @Autowired
    private DataSourceInterceptor dataSourceInterceptor;
  
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(dataSourceInterceptor).addPathPatterns("/**");
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}


Now we are all done with the configurations and implementation of AbstractRoutingDatasource. Now it’s time to create the Repository, Service, and Controller layer of our application. 

Java




package com.gfg.scripter.repository;
  
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.gfg.scripter.entity.Intern;
  
@Repository
public interface InternRepository extends JpaRepository<Intern, Long> {}


Java




package com.gfg.scripter.service;
  
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.gfg.scripter.entity.Intern;
import com.gfg.scripter.repository.InternRepository;
  
@Service
public class InternService {
  
    @Autowired
    private InternRepository internRepository;
  
    public List<Intern> getInterns() {
        return internRepository.findAll();
    }
}


Java




package com.gfg.scripter.controller;
  
import java.util.List;
  
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
  
import com.gfg.scripter.entity.Intern;
import com.gfg.scripter.service.InternService;
  
@RestController
public class InternController {
  
    @Autowired
    private InternService internService;
  
    @GetMapping(value = "intern")
    public ResponseEntity<List<Intern>> getInterns() {
        return ResponseEntity.status(HttpStatus.ACCEPTED)
                .body(internService.getInterns());
    }
}


Test the application: We can use any client tool to test/hit the endpoint. Here we can go with one of the popular tools i.e. Postman. When we will hit the endpoint http://localhost:8080/interns then we have to pass one additional parameter i.e. branch like below:

postman endpoint calling

 



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads