In large applications, it is a common design pattern to configure master-slave databases and use read-write separation. In Spring
applications, to implement read-write separation, it is best to not make changes to existing code, but to support it transparently at the bottom.
Spring
has a built-in AbstractRoutingDataSource
that can configure multiple data sources into a Map and then, depending on the key, return different data sources. Because AbstractRoutingDataSource
is also a DataSource
interface, the application can set the key
first, and the code that accesses the database can get the corresponding real data source from AbstractRoutingDataSource
to access the specified database. Its structure looks like this:
|
|
Step 1: Configure multiple data sources
First, we configure two data sources in SpringBoot
, the second of which is ro-datasource
|
|
In the development environment, there is no need to configure a master-slave database. It is only necessary to set two users to the database, one rw
with read and write permissions and one ro
with only SELECT
permissions, which simulates the read and write separation of the master and slave databases in the production environment.
In the SpringBoot
configuration code, we initialize two data sources:
|
|
Step 2: Write RoutingDataSource
Then, we use Spring
s built-in RoutingDataSource
to proxy two real data sources into one dynamic data source:
For this RoutingDataSource
, you need to configure it in SpringBoot
and set it as the primary data source:
|
|
Now, the RoutingDataSource
is configured, but the routing is written dead, i.e. it always returns the masterDataSource
Now comes the question: how to store the dynamically selected key
and where to set the key
?
In the threaded model of Servlet
, it is most appropriate to use ThreadLocal
to store the key
, so we write a RoutingDataSourceContext
to set and dynamically store the key
|
|
Then, modify the RoutingDataSource
to get the key
code as follows:
This way, somewhere, for example inside a Controller
method, the Key of the DataSource
can be set dynamically:
Up to this point, we have successfully implemented dynamic routing access to the database.
This method works, but where you need to read from the database, you need to add a big try (RoutingDataSourceContext ctx = ...) {}
code, which is very inconvenient to use. Is there a way to simplify it?
Yes!
If we think about it, the declarative transaction management provided by Spring
only requires a @Transactional()
annotation, placed on a Java
method, which automatically has a transaction.
We can also write a similar @RoutingWith("slaveDataSource")
annotation and put it on a Controller
method, which automatically selects the corresponding data source internally. The code should look like this:
This is the easiest way to automatically implement dynamic data source switching without modifying the application logic at all, and only adding annotations where necessary.
To write less code in the application, we have to do a little more work at the bottom: we have to use a mechanism similar to Spring’s implementation of declarative transactions, i.e. dynamic data source switching with AOP
.
Implementing this is also very simple, write a RoutingAspect
and use AspectJ
to implement an Around
intercept:
|
|
Note that the second parameter of the method RoutingWith
is an instance of an annotation passed in by Spring
, and we get the configured key
according to the value()
of the annotation. You need to add a Maven
dependency before compiling:
At this point, we have implemented the ability to dynamically select data sources with annotations. The last refactoring step is to replace the masterDataSource
and slaveDataSource
scattered all over the place with string constants:
Usage restrictions
Limited by the Servlet
thread model, dynamic data sources cannot be set and then modified within a request, i.e. @RoutingWith
cannot be nested. In addition, when @RoutingWith
and @Transactional
are mixed, the priority of AOP
should be set
Reference https://www.liaoxuefeng.com/article/1182502273240832