Step 1: configure multiple data sources
I won't introduce the basics of Spring Boot. I recommend this practical tutorial: github.com/javastacks/...
First, we configure two data sources in SpringBoot. The second data source is ro datasource:
spring: datasource: jdbc-url: jdbc:mysql://localhost/test username: rw password: rw_password driver-class-name: com.mysql.jdbc.Driver hikari: pool-name: HikariCP auto-commit: false ... ro-datasource: jdbc-url: jdbc:mysql://localhost/test username: ro password: ro_password driver-class-name: com.mysql.jdbc.Driver hikari: pool-name: HikariCP auto-commit: false ... Copy code
In the development environment, it is not necessary to configure the master-slave database. Only two users need to be set for the database. One rw has read-write permission and the other ro has SELECT permission. This simulates the read-write separation of the master-slave database in the production environment.
In the SpringBoot configuration code, we initialize two data sources:
@SpringBootApplication public class MySpringBootApplication { /** * Master data source. */ @Bean("masterDataSource") @ConfigurationProperties(prefix = "spring.datasource") DataSource masterDataSource() { logger.info("create master datasource..."); return DataSourceBuilder.create().build(); } /** * Slave (read only) data source. */ @Bean("slaveDataSource") @ConfigurationProperties(prefix = "spring.ro-datasource") DataSource slaveDataSource() { logger.info("create slave datasource..."); return DataSourceBuilder.create().build(); } ... } Copy code
Step 2: write RoutingDataSource
Then, we use Spring's built-in RoutingDataSource to proxy two real data sources into a dynamic data source:
public class RoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return "masterDataSource"; } } Copy code
For this RoutingDataSource, you need to configure it in SpringBoot and set it as the main data source:
@SpringBootApplication public class MySpringBootApplication { @Bean @Primary DataSource primaryDataSource( @Autowired @Qualifier("masterDataSource") DataSource masterDataSource, @Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource ) { logger.info("create routing datasource..."); Map<Object, Object> map = new HashMap<>(); map.put("masterDataSource", masterDataSource); map.put("slaveDataSource", slaveDataSource); RoutingDataSource routing = new RoutingDataSource(); routing.setTargetDataSources(map); routing.setDefaultTargetDataSource(masterDataSource); return routing; } ... } Copy code
Now, the RoutingDataSource is configured, but the route selection is dead, that is, it always returns "masterDataSource",
Now the question comes: how to store the dynamically selected key and where to set the key?
In the Servlet thread model, ThreadLocal is the most appropriate way to store keys. Therefore, we write a RoutingDataSourceContext to set and dynamically store keys:
public class RoutingDataSourceContext implements AutoCloseable { // holds data source key in thread local: static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>(); public static String getDataSourceRoutingKey() { String key = threadLocalDataSourceKey.get(); return key == null ? "masterDataSource" : key; } public RoutingDataSourceContext(String key) { threadLocalDataSourceKey.set(key); } public void close() { threadLocalDataSourceKey.remove(); } } Copy code
Then, modify the RoutingDataSource to obtain the key code as follows:
public class RoutingDataSource extends AbstractRoutingDataSource { protected Object determineCurrentLookupKey() { return RoutingDataSourceContext.getDataSourceRoutingKey(); } } Copy code
In this way, the Key of the DataSource can be dynamically set somewhere, such as within a Controller method:
@Controller public class MyController { @Get("/") public String index() { String key = "slaveDataSource"; try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) { // TODO: return "html... www.liaoxuefeng.com"; } } } Copy code
So far, we have successfully realized the dynamic routing access of the database.
This method is feasible, but a large section of try (RoutingDataSourceContext ctx =...) needs to be added where the slave database needs to be read {} code, very inconvenient to use. Is there any way to simplify it?
have
Let's think about it carefully. The declarative transaction management provided by Spring only needs an @ Transactional() annotation on a Java method, which automatically has transactions.
We can also write a similar @ RoutingWith("slaveDataSource") annotation and put it on a Controller method, which automatically selects the corresponding data source. The code should look like this:
@Controller public class MyController { @Get("/") @RoutingWith("slaveDataSource") public String index() { return "html... www.liaoxuefeng.com"; } } Copy code
In this way, the logic of the application program is not modified at all, and only annotations are added where necessary to automatically realize dynamic data source switching. This method is the simplest.
If we want to write less code in the application, we have to do more underlying work: we must use a mechanism similar to Spring to implement declarative transactions, that is, AOP to realize dynamic data source switching.
It is also very simple to implement this function. Write a RoutingAspect and use AspectJ to implement an Around interception:
@Aspect @Component public class RoutingAspect { @Around("@annotation(routingWith)") public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWith routingWith) throws Throwable { String key = routingWith.value(); try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) { return joinPoint.proceed(); } } } Copy code
Note that the second parameter RoutingWith of the method is the annotation instance passed in by Spring. We get the configured key according to the annotation value(). You need to add a Maven dependency before compiling:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> Copy code
So far, we have realized the function of dynamically selecting data sources with annotations. The last step of refactoring is to replace the scattered "masterDataSource" and "slaveDataSource" with string constants.
Use restrictions
Due to the limitation of Servlet thread model, dynamic data sources cannot be set in a request and then modified, that is, @ RoutingWith cannot be nested. In addition, when @ RoutingWith and @ Transactional are mixed, AOP priority should be set.
The code in this article needs spring boot support. JDK 1.8 compiles and opens the - parameters compilation parameter.
Original address: Home - Liao Xuefeng's official website
If you think this article is helpful to you, please praise it, pay attention and support it