In a word: use dynamic data sources to operate multiple databases, flexible and concise.
1. The introduction
For the processing of multiple databases, the last article “Get SpringBoot multi-source (1) : multiple sets of source strategy” has been mentioned, there are multiple sets of data sources, dynamic data sources, parameterized change data sources and other ways, this article is the second: “dynamic data sources”. Dynamic data sources can solve the problems of inflexible processing of multiple data sources and occupying too many resources. Users can unify the operation logic based on actual service requirements, as long as the data source needs to be switched for processing. What is dynamic is that the timing of batch switching data sources can be dynamically selected and switched where necessary.
This article continues the example of the last article, with the master-slave scenario as an example, combined with the code, to explain the implementation of dynamic data source, including the principle of building dynamic data source, dynamic data source configuration, dynamic data source use, AOP annotation switch data source, etc..
This paper involved the sample code: https://github.com/mianshenglee/my-example/tree/master/multi-datasource, readers can combine together.
2. Dynamic data source process description
Spring Boot’s dynamic data source essentially stores multiple data sources in a Map. When a data source needs to be used, the Map obtains the data source for processing. In the Spring, has provided an abstract class AbstractRoutingDataSource to implement this function. Therefore, we in the implementation of dynamic data sources, just need to inherit it, to achieve their own access to the data source logic. The dynamic data source flow is as follows:
When accessing applications, users can access different data sources based on the routing logic of data sources and perform operations on corresponding data sources. The two databases in this example have a table test_user, which has the same structure. For the sake of illustration, the data in the two tables are different. The two table structures are available in the SQL directory in the sample code.
3. Dynamic data sources
3.1 Description and Data Source Configuration
3.1.1 Package Structure description
In this example, there are the following packages:
- ├ ─ an annotation// Custom annotations├ ─ aop -- -- -- -- -- -- -- -- -- -- -/ / section├ ─ config -- -- -- -- -- -- -- --// Data source configuration├ ─ constants -- -- -- -- --// Common annotations├ ─ the context -- -- -- -- -- -- --// Customize the context
├─controller ---- // Access the interface├ ─ the entity -- -- -- -- -- -- -- --/ / entity├ ─ mapper -- -- -- -- -- -- -- --// Database DAO operations├ ─ service -- -- -- -- -- -- --/ / service class└ ─ vo -- -- -- -- -- -- -- -- -- -- -- --// The view returns data
Copy the code
3.1.2 Configuring database Connection Information
The default configuration file for Spring Boot is application.properties. Since there are two database configurations, it is good practice to configure the database independently, so add the configuration file jbdc.properties to add the following custom master/slave database configurations:
# master spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/mytest? useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.master.username=root spring.datasource.master.password=111111 # slave spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/my_test1? useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.slave.username=root spring.datasource.slave.password=111111Copy the code
3.1.3 Data source Configuration
Add the DynamicDataSourceConfig file to Spring based on the connection information:
@Configuration
@PropertySource("classpath:config/jdbc.properties")
@MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper")
public class DynamicDataSourceConfig {
@Bean(DataSourceConstants.DS_KEY_MASTER)
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(a) {
return DataSourceBuilder.create().build();
}
@Bean(DataSourceConstants.DS_KEY_SLAVE)
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(a) {
returnDataSourceBuilder.create().build(); }}Copy the code
Note:
- Used here
PropertySource
Specify the configuration file,ConfigurationProperties
Specify the data source configuration prefix- use
MapperScan
Specify the package to automatically inject the corresponding Mapper class.- Write the data source constant at
DataSourceConstants
In the class- As you can see from this configuration, the SqlSessionFactory configuration has been erased from the code and you can use the SqlSessionFactory configuration automatically configured by Spring Boot.
3.2 Dynamic data source Settings
The previous configuration injected multiple data sources into Spring and then configured the dynamic data sources.
3.2.1 Dynamic Data Source Configuration
(1) Add JDBC dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
Copy the code
(2) Add dynamic data source class
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey(a) {
// Return the fixed master data source for the time being
returnDataSourceConstants.DS_KEY_MASTER; }}Copy the code
Note:
- Inheriting abstract classes
AbstractRoutingDataSource
Implementation methods are requireddetermineCurrentLookupKey
Is the routing policy.- The dynamic routing policy is implemented next. The current policy returns the master data source directly
(3) Set dynamic data source as main data source
In the previous data source configuration file DynamicDataSourceConfig, add the following code:
@Bean
@Primary
public DataSource dynamicDataSource(a) {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
// Set the dynamic data source
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
Copy the code
- Use Map to save multiple data sources and set them up in a dynamic data source object.
- Set the default data source to be master data source
- Using annotations
Primary
Preferentially obtained from dynamic data sources
At the same time, need in DynamicDataSourceConfig, eliminate the automatic configuration of DataSourceAutoConfiguration The dependencies of some of The beans in The application context form a cycle
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
Copy the code
3.2.2 Dynamic Data source selection
** (1) Context of the data source key **
We fixed a data source routing policy that always returns master, which is obviously not what we want. We want to be where we want to be, and we want to switch. Therefore, there needs to be a place to dynamically obtain the data source key (we call it a context). For Web applications, where access is thread-based, ThreadLocal is appropriate, as follows:
public class DynamicDataSourceContextHolder {
/** * Dynamic data source name context */
private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
/** * Set/switch data source */
public static void setContextKey(String key){
DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
}
/** * get the data source name */
public static String getContextKey(a){
String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
return key == null? DataSourceConstants.DS_KEY_MASTER:key; }/** * Delete the current data source name */
public static void removeContextKey(a){
DATASOURCE_CONTEXT_KEY_HOLDER.remove();
}
Copy the code
Storing DATASOURCE_CONTEXT_KEY_HOLDER requires a data source key
When getContextKey is null, master is returned by default
(2) Set dynamic dataDynamicDataSource
Routing strategy
The routing policy we need to achieve is that when we set the data source key to the context, we get the data source key from the context and know which data source to use. Therefore, modify the previous DynamicDataSource determineCurrentLookupKey method is as follows:
@Override
protected Object determineCurrentLookupKey(a) {
return DynamicDataSourceContextHolder.getContextKey();
}
Copy the code
3.2.3 Use of dynamic data sources
With the above dynamic routing selection, there is no need to write the same logic code for Mapper, Entity, service, etc., as the previous multiple sets of data sources. Because the database structure is generally consistent, only one set of Entity, Mapper, service is needed. When you need to operate on different data sources, you can simply set the context. As follows:
@RestController
@RequestMapping("/user")
public class TestUserController {
@Autowired
private TestUserMapper testUserMapper;
/** * query all */
@GetMapping("/listall")
public Object listAll(a) {
int initSize = 2;
Map<String, Object> result = new HashMap<>(initSize);
// Default master query
QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
List<TestUser> resultData = testUserMapper.selectAll(queryWrapper.isNotNull("name"));
result.put(DataSourceConstants.DS_KEY_MASTER, resultData);
// Switch data source to slave
DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_SLAVE);
List<TestUser> resultDataSlave = testUserMapper.selectList(null);
result.put(DataSourceConstants.DS_KEY_SLAVE, resultDataSlave);
// Restore the data source
DynamicDataSourceContextHolder.removeContextKey();
// Return data
returnResponseResult.success(result); }}Copy the code
- The default is to use the master data source query
- Use the setContextKey of the context to switch the data source, and then use the removeContextKey to restore the data source
3.3 Use AOP to select data sources
After the above dynamic data source configuration, dynamic data source switch can be implemented, but we will find that when switching data source, setContextKey and removeContextKey operations need to be done, if there are more methods to switch, we will find a lot of repeated code, how to eliminate these repeated code, If you do not understand dynamic proxies, you can refer to my article “Java Development must Learn: Dynamic proxies”. In Spring, AOP implementations are also based on dynamic proxies. Here, we want to annotate the data source required by the function to eliminate the template code of the product when the data source is switched.
3.3.1 Define data source annotations
In the Annotation package, add the data source annotation DS, which can be written in the class or in the method definition, as follows:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
/** * Data source name */
String value(a) default DataSourceConstants.DS_KEY_MASTER;
}
Copy the code
3.3.2 Defining data source facets
Defines a data source aspect that can be used to switch data sources for methods or classes that use DS annotations.
(1) Add AOP dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Copy the code
(2) Define the section
@Aspect
@Component
public class DynamicDataSourceAspect {
@Pointcut("@annotation(me.mason.demo.dynamicdatasource.annotation.DS)")
public void dataSourcePointCut(a){}@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsKey = getDSAnnotation(joinPoint).value();
DynamicDataSourceContextHolder.setContextKey(dsKey);
try{
return joinPoint.proceed();
}finally{ DynamicDataSourceContextHolder.removeContextKey(); }}/** * Get data source annotations by class or method */
private DS getDSAnnotation(ProceedingJoinPoint joinPoint){ Class<? > targetClass = joinPoint.getTarget().getClass(); DS dsAnnotation = targetClass.getAnnotation(DS.class);// Judge the class annotations first, and then the method annotations
if(Objects.nonNull(dsAnnotation)){
return dsAnnotation;
}else{
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
returnmethodSignature.getMethod().getAnnotation(DS.class); }}}Copy the code
- Annotations Pointcut use
annotation
Specify the annotation- Annotations Around use the surround notification processing, and use the context for annotations
DS
After processing, the data source is restored.
3.3.3 Use AOP to switch data sources
In the service layer, we define a TestUserService with two methods, one from master and the other from slave, using the DS annotation as follows:
/** * query User */
@DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getMasterUser(a){
QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/** * query slave library User */
@DS(DataSourceConstants.DS_KEY_SLAVE)
public List<TestUser> getSlaveUser(a){ return testUserMapper.selectList(null); }
Copy the code
After this definition, the processing at the Controller layer can become:
@GetMapping("/listall")
public Object listAll(a) {
int initSize = 2;
Map<String, Object> result = new HashMap<>(initSize);
// Default master data source query
List<TestUser> masterUser = testUserService.getMasterUser();
result.put(DataSourceConstants.DS_KEY_MASTER, masterUser);
// Query from the slave data source
List<TestUser> slaveUser = testUserService.getSlaveUser();
result.put(DataSourceConstants.DS_KEY_SLAVE, slaveUser);
// Return data
return ResponseResult.success(result);
}
Copy the code
As you can see, the template code for database switching has been eliminated and you only need to focus on business logic processing. This is the benefit of AOP.
4. Think again
From the dynamic data sources and AOP’s choice of data sources explained above, we can see that dynamic data sources are already flexible enough to switch simply by setting the data source in the context, or by using annotations directly in a method or class. Now we are manually coded to achieve, in fact, for MyBatis Plus, it also provides a dynamic data source plug-in, interested partners can also according to its official documents for experimental use.
What else could be considered or improved for dynamic data sources?
- How are transactions handled? Cross-library transactions should be avoided as much as possible in development, but if you can’t avoid them, you need to use distributed transactions.
- For the current dynamic data sources, relatively fixed data sources (such as master one slave, one master many slave, etc.), that is, the number of databases has been determined at the time of coding, and it is only processed dynamically when the specific use of which one. What if the data source itself is uncertain, or you need to connect to the database based on user input? This happens more often when managing multiple databases. This situation, which I’ll cover in the next article, is called “parameterized change data sources.”
5. To summarize
This paper explains the implementation of dynamic data sources, mainly the configuration, implementation, use of dynamic data sources, and also use AOP to eliminate the template code when switching data sources, so that we focus on business code development, and finally the dynamic data sources of the expansion of thinking. I hope you can master the processing of dynamic data sources.
This article supporting examples, sample code, are interested in running examples to feel.
The resources
- Spring master/slave database configuration and dynamic data source switching principle:
https://www.liaoxuefeng.com/article/1182502273240832
- Trade-offs between multiple and dynamic data sources:
https://juejin.cn/post/6844903661655572494
- Talk about Spring Boot data source loading and its simple implementation of multiple data sources:
https://juejin.cn/post/6844903817260056589
- Spring Boot and MyBatis realize multi-data source and dynamic data source switching:
https://juejin.cn/post/6844903566717485069
The articles
- SpringBoot Multiple Data Sources (1) : Multiple source policies
- Java development must learn: dynamic proxy
- Good books to read in 2019
- Springboot + Apache HTTPS is deployed on the front and back ends
- Springboot + Logback Log Output
- Springboot + Logback Log output
My official account (search Mason technical records) for more technical records: