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:
Create a maven project with the required dependencies in the pom.xml file
XML
<? xml version = "1.0" encoding = "UTF-8" ?>
< 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;
}
|
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);
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:
Feeling lost in the vast world of Backend Development? It's time for a change! Join our
Java Backend Development - Live Course and embark on an exciting journey to master backend development efficiently and on schedule.
What We Offer:
- Comprehensive Course
- Expert Guidance for Efficient Learning
- Hands-on Experience with Real-world Projects
- Proven Track Record with 100,000+ Successful Geeks
Last Updated :
15 Nov, 2022
Like Article
Save Article