Dynamic Data Source Routing in Spring Boot using AbstractRoutingDataSource
A very common use case that we generally get while building services is to enable multiple data sources in the same service where the connectivity to either of them is capable at run time with a single instance of the application running.
This problem is more commonly known as the multi tenant data architecture with a single application connecting to multiple data sources (There are 3 scenarios for multiple data sources but I will not be delving into that), although there are ways to create multiple entity managers etc and create multiple data sources but that requires a lot of code and too much of maintenance. So, to solve this problem spring-jdbc provides us with a beautiful concept called the AbstractRoutingDataSource.
The definition from Spring documentation:
AbstractRoutingDataSource is an abstract DataSource implementation that routes getConnection() calls to one of various target DataSources based on a lookup key. The latter is usually (but not necessarily) determined through some thread-bound transaction context.
To put more concisely, there are four things that we need to implement dynamic data source routing using AbstractRoutingDataSource:
- A component that extends AbstractRoutingDataSource and is responsible to provide the list of datasources and also to provide the implementation of the determineCurrentLookupKey() method which will help in determining the current datasource.
- A context holder component that determines the context which is better to be thread bound by using Threadlocal so that the context is bound to the currently executing thread.
- A context, generally an enum which serves as the lookup key to determine the current datasource.
- A configuration class which create the primary datasource bean, the entity manager and the transaction bean.
We are going to save employee data into the database where there will be only one table employee.
Let’s jump into the code straightaway!
Setting up the data sources using MySQL
In MySQL, run these scripts to create the two databases each of which having a table employee having the same structure. These two different databases will serve as the two data sources for the application.
1. The Maven Dependencies
2. The Project Structure:
3. The Employee Entity
4. The Employee Repository
5. The Employee Service
6. The application.properties file
7. The Configuration file for the first data source
8. The Configuration file for the second data source
9. The Context
The context file is represented as an enum which serves as the lookup key for a data source.
10. The Context Holder
The context holder is a component that serves as a store for the thread bound context. There are member methods which can be used to set, retrieve and delete the context.
11. The Component responsible for routing to the appropriate DataSource
In this we create two methods:
- dataSourceTwoDataSource()
- dataSourceOneDataSource()
These two methods creates an object of DataSource type containing the configurations of the first and second datasource respectively. These methods are then used to set the list of target data sources to which the application can route to during run time.
The initialization of the target datasources are done in the constructor so that the target data sources are listed when the component is created. The listing is done using the setTargetDataSources() method which takes in a map of data source key to the DataSource object.
12. The Data Source Configuration
The DataSourceConfig file creates a DataSource bean which essentially returns the DataSourceRouting component that we had created earlier. It is due to this exact reason, we have added a @DependsOn annotation which ensures that the configuration bean is created once our routing DataSource is created.
13. The Controller
In the controller I am taking the context as a path variable and then using the context holder to set the current data source context.
14. Running the application
The runner class:
Once the application starts running,
To check the details of the first data source, go to http://localhost:8081/getEmployeeDetails/DATASOURCE_ONE
To check the details of the second data source, go to http://localhost:8081/getEmployeeDetails/DATASOURCE_TWO
Conclusion
So, I have tried to explain the use case and how we will be using AbstractRoutingDataSource provided by Spring to elegantly solve the problem. You can go through the entire article and understand the nook and crannies on how it works and do comment if there are parts which you are confused about.
The code can be found here.