Spring – AbstractRoutingDataSource
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 w3wiki 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" ?> < project xmlns = "http://maven.apache.org/POM/4.0.0" 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:
Contact Us