Some time ago when I was doing the development of member center and middleware system, I encountered many configuration problems of multi-data sources. I mainly used the four methods of subcontracting, parameterized switching, annotation +AOP and dynamic addition. Here is a summary, share the experience of using and step on the pit.
Four ways to compare
The article is quite long, first gives the comparison of four implementation methods, you can choose to read according to their own needs.
sub-contracting | Parameterized switching | Annotation way | Dynamic add mode | |
---|---|---|---|---|
Applicable scenario | When you code, you know which data source to use | The runtime determines which data source to use | When you code, you know which data source to use | The runtime dynamically adds new data sources |
Switch mode | automatic | manual | automatic | manual |
Mapper path | Need to subcontract | No request | No request | No request |
Whether the configuration class needs to be modified to add a data source | Need to be | Don’t need | Don’t need | \ |
Implementation complexity | simple | complex | complex | complex |
sub-contracting
Data source configuration file
In YML, configure two data sources with ids master and s1.
spring:
datasource:
master:
jdbcUrl: JDBC: mysql: / / 192.168. XXX. XXX: XXXX/db1? .
username: xxx
password: xxx
driverClassName: com.mysql.cj.jdbc.Driver
s1:
jdbcUrl: JDBC: mysql: / / 192.168. XXX. XXX: XXXX/db2? .
username: xxx
password: xxx
driverClassName: com.mysql.cj.jdbc.Driver
Copy the code
Data source configuration class
Master data source configuration class
Note:
You need to specify the default data source with the @primary annotation, otherwise Spring doesn’t know which is the Primary data source;
@Configuration
@MapperScan(basePackages = "com.hosjoy.xxx.xxx.xxx.xxx.mapper.master", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {
// Default data source
@Bean(name = "masterDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public HikariDataSource masterDataSource(a) {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "masterSqlSessionFactory")
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource datasource, PaginationInterceptor paginationInterceptor)
throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(datasource);
bean.setMapperLocations(
// Set the XML location of mybatis
new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/master/**/**.xml"));
bean.setPlugins(new Interceptor[]{paginationInterceptor});
return bean.getObject();
}
@Bean(name = "masterSqlSessionTemplate")
@Primary
public SqlSessionTemplate masterSqlSessionTemplate(
@Qualifier("masterSqlSessionFactory") SqlSessionFactory sessionfactory) {
return newSqlSessionTemplate(sessionfactory); }}Copy the code
S1 Data source configuration class
@Configuration
@MapperScan(basePackages = "com.hosjoy.xxx.xxx.xxx.xxx.mapper.s1", sqlSessionFactoryRef = "s1SqlSessionFactory")
public class S1DataSourceConfig {
@Bean(name = "s1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.s1")
public HikariDataSource s1DataSource(a) {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "s1SqlSessionFactory")
public SqlSessionFactory s1SqlSessionFactory(@Qualifier("s1DataSource") DataSource datasource
, PaginationInterceptor paginationInterceptor)
throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(datasource);
bean.setMapperLocations(
// Set the XML location of mybatis
new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/s1/**/**.xml"));
bean.setPlugins(new Interceptor[]{paginationInterceptor});
return bean.getObject();
}
@Bean(name = "s1SqlSessionTemplate")
public SqlSessionTemplate s1SqlSessionTemplate(
@Qualifier("s1SqlSessionFactory") SqlSessionFactory sessionfactory) {
return newSqlSessionTemplate(sessionfactory); }}Copy the code
use
As can be seen, mapper interfaces and XML files need to be subcontracted according to different data sources. When working with the database, inject the DAO layer into the Service class as needed.
Characteristics analysis
advantages
The implementation is simple, only need to write data source configuration file and configuration class, Mapper interface and XML file pay attention to subcontracting.
disadvantages
Obviously, if you later add or remove data sources, you will need to modify not only the data source configuration file, but also the configuration class.
For example, if you add a data source, you also need to write a new configuration class for the data source, and you also need to create a Mapper interface package and XML package, which does not achieve the “hot swap” effect.
Parameterized switchover mode
thought
Parameterized switching data sources means that the service side needs to dynamically switch to different data sources based on the current service parameters.
This is different from the idea of subcontracting. The premise of subcontracting is that at the time of writing the code, you already know which data source you need to use, whereas parameterized switching data sources requires deciding which data source to use based on business parameters.
For example, if the request parameter userType is 1, the data source slave1 needs to be switched. If the request parameter userType is 2, switch to slave2.
/** pseudocode **/
int userType = reqUser.getType();
if (userType == 1) {// Switch to data source slave1
// Database operation
} else if(userType == 2) {// Switch to data source slave2
// Database operation
}
Copy the code
Design ideas
Data source registration
Data source configuration class to create a datasource, read all data source configuration, from yml configuration file is automatically created each data source, and register to the bean plants and AbstractRoutingDatasource chats behind (this), at the same time to return to the default master data source.
Data source switching
(1) Requests are processed through the thread pool, and each request has an exclusive thread, so that each thread does not have any influence when switching data sources.
(2) Obtain the ID of the data source to be switched according to the business parameters, and obtain the data source bean from the data source cache pool according to the ID;
(3) Generate the current thread data source key;
Set key to threadLocal;
(5) Put the key and data source bean into the data source cache pool;
(6) before performing mapper method, spring will call determineCurrentLookupKey method to get the key, and then according to the key to remove data source, data cache pool then getConnection access to the data source connection;
(7) Use the data source to perform database operations;
(8) Release the current thread data source.
AbstractRoutingDataSource source code analysis
Spring provides us with AbstractRoutingDataSource abstract class, the class is the key to switch to implement dynamic data source.
Let’s look at the class diagram of this class, which implements the DataSource interface.
We look at it the getConnection method of logic, the first call determineTargetDataSource to obtain data sources, to obtain a database connection. It’s easy to guess that this is where the decision is made about which data source to use.
Into the determineTargetDataSource method, we can see it first calls determineCurrentLookupKey get a lookupKey, Then go to resolvedDataSources to find the corresponding data source according to this key.
DefaultTargetDataSource is the default data source, and resolvedDataSources is a map object that stores all primary and secondary data sources.
So the key is that the fetch logic of this lookupKey determines which data source is being fetched, and then performs a series of operations such as getConnection. DetermineCurrentLookupKey AbstractRoutingDataSource is an abstract method in class, and the return value is the source of data for all you have to use a dataSource key values, with the key values, The resolvedDataSource is a map that is stored in the configuration file. If it cannot be found, the default DataSource is used.
So, by extending the AbstractRoutingDataSource classes, and rewrite the determineCurrentLookupKey () method, which can realize data source switching.
Code implementation
The key code implementation is posted below.
Data source configuration file
There are 3 data sources configured, where the primary data source is MySQL and the two secondary data sources are SQLServer.
spring:
datasource:
master:
jdbcUrl: JDBC: mysql: / / 192.168 xx, XXX, XXX/db1? .
username: xxx
password: xxx
driverClassName: com.mysql.cj.jdbc.Driver
slave1:
jdbcUrl: JDBC: essentially: / / 192.168 xx, XXX, XXX. DatabaseName=db2
username: xxx
password: xxx
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
slave2:
jdbcUrl: JDBC: essentially: / / 192.168 xx, XXX, XXX. DatabaseName=db3
username: xxx
password: xxx
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
Copy the code
Defining dynamic data sources
Mainly inherited AbstractRoutingDataSource, realize determineCurrentLookupKey method.
public class DynamicDataSource extends AbstractRoutingDataSource {
/* Store all data sources */
private Map<Object, Object> backupTargetDataSources;
public Map<Object, Object> getBackupTargetDataSources(a) {
return backupTargetDataSources;
}
/*defaultDataSource is the defaultDataSource */
public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSource) {
backupTargetDataSources = targetDataSource;
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(backupTargetDataSources);
super.afterPropertiesSet();
}
public void addDataSource(String key, DataSource dataSource) {
this.backupTargetDataSources.put(key, dataSource);
super.setTargetDataSources(this.backupTargetDataSources);
super.afterPropertiesSet();
}
/* Returns the key of the current thread's data source */
@Override
protected Object determineCurrentLookupKey(a) {
returnDynamicDataSourceContextHolder.getContextKey(); }}Copy the code
Define data source key thread variable hold
Define a static ThreadLocal variable that holds the relationship between a thread and its data source key. When switching data sources, we first generate a key ourselves and store this key in a threadLocal thread variable. You should also get the data source object from the backupTargetDataSources property in the DynamicDataSource object, and then put the key and data source object into the backupTargetDataSources. In this way, the spring can, according to a key determineCurrentLookupKey method returns from the backupTargetDataSources we just set the data source object, getConnection and so on a series of operations.
public class DynamicDataSourceContextHolder {
/** * Stores the mapping between threads and data source keys */
private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
/*** * Sets the current thread data source key */
public static void setContextKey(String key) {
DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
}
/*** * Gets the current thread data source key */
public static String getContextKey(a) {
String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
return key == null ? DataSourceConstants.DS_KEY_MASTER : key;
}
/*** * Deletes the current thread data source key */
public static void removeContextKey(a) {
DynamicDataSource dynamicDataSource = RequestHandleMethodRegistry.getContext().getBean(DynamicDataSource.class);
String currentKey = DATASOURCE_CONTEXT_KEY_HOLDER.get();
if (StringUtils.isNotBlank(currentKey) && !"master".equals(currentKey)) { dynamicDataSource.getBackupTargetDataSources().remove(currentKey); } DATASOURCE_CONTEXT_KEY_HOLDER.remove(); }}Copy the code
Multi-source automatic configuration class
Here, the datasource object is automatically created for each datasource and registered with the bean factory by reading the configuration for all the data sources in the yml configuration file. At the same time the data source object, set in the AbstractRoutingDataSource.
In this way, if the data source needs to be added or modified later, there is no need to add or modify the Java configuration class, just go to the configuration center and modify the YML file.
@Configuration
@MapperScan(basePackages = "com.hosjoy.xxx.xxx.modules.xxx.mapper")
public class DynamicDataSourceConfig {
@Autowired
private BeanFactory beanFactory;
@Autowired
private DynamicDataSourceProperty dynamicDataSourceProperty;
<br> * < dynamic data source bean automatic configuration register all data sources > **@param
* @return javax.sql.DataSource
* @Author li.he
* @Date2020/6/4 yet *@Modifier* /
@Bean
@Primary
public DataSource dynamicDataSource(a) {
DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory;
/* Get all data source configurations of YML */
Map<String, Object> datasource = dynamicDataSourceProperty.getDatasource();
Map<Object, Object> dataSourceMap = new HashMap<>(5);
Optional.ofNullable(datasource).ifPresent(map -> {
for (Map.Entry<String, Object> entry : map.entrySet()) {
// Create a data source object
HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder.create().build();
String dataSourceId = entry.getKey();
configeDataSource(entry, dataSource);
/* The bean factory registers each data source bean*/listableBeanFactory.registerSingleton(dataSourceId, dataSource); dataSourceMap.put(dataSourceId, dataSource); }});/ / AbstractRoutingDataSource master-slave data set
return new DynamicDataSource(beanFactory.getBean("master", DataSource.class), dataSourceMap);
}
private void configeDataSource(Map.Entry<String, Object> entry, HikariDataSource dataSource) {
Map<String, Object> dataSourceConfig = (Map<String, Object>) entry.getValue();
dataSource.setJdbcUrl(MapUtils.getString(dataSourceConfig, "jdbcUrl"));
dataSource.setDriverClassName(MapUtils.getString(dataSourceConfig, "driverClassName"));
dataSource.setUsername(MapUtils.getString(dataSourceConfig, "username"));
dataSource.setPassword(MapUtils.getString(dataSourceConfig, "password")); }}Copy the code
Data source switching utility class
Switch logic:
(1) Generate the data source key of the current thread
(2) According to business conditions, obtain the ID of the data source to be switched;
(3) Get the data source object from the data source cache pool according to the ID, and add it to the backupTargetDataSources cache pool again;
ThreadLocal sets the data source key corresponding to the current thread;
(5) before perform database operations, spring will call determineCurrentLookupKey method to obtain the key, and then according to the key to remove data source, data cache pool then getConnection access to the data source connection;
(6) Use the data source to perform database operations;
(7) Release cache: threadLocal cleans up the data source information of the current thread, and the data source cache pool cleans up the data source key and data source object of the current thread to prevent memory leaks.
@Slf4j
@Component
public class DataSourceUtil {
@Autowired
private DataSourceConfiger dataSourceConfiger;
/* Switch data sources based on business conditions */
public void switchDataSource(String key, Predicate<? super Map<String, Object>> predicate) {
try {
// Generate the current thread data source key
String newDsKey = System.currentTimeMillis() + "";
List<Map<String, Object>> configValues = dataSourceConfiger.getConfigValues(key);
Map<String, Object> db = configValues.stream().filter(predicate)
.findFirst().get();
String id = MapUtils.getString(db, "id");
// Get the data source object from the data source cache pool by ID and add it again to backupTargetDataSources
addDataSource(newDsKey, id);
// Set the data source key for the current thread
DynamicDataSourceContextHolder.setContextKey(newDsKey);
log.info("Current thread data source switched successfully, current data source ID:{}", id);
}
catch (Exception e) {
log.error("Switching data source failed. Please check the data source configuration file. Key: {}, conditions: {}", key, predicate.toString());
throw new ClientException("Failed to switch data source. Please check data source configuration.", e); }}/* Add the data source to the cache pool */
public static void addDataSource(String key, String dataSourceId) { DynamicDataSource dynamicDataSource = RequestHandleMethodRegistry.getContext().getBean(DynamicDataSource.class); DataSource dataSource = (DataSource) dynamicDataSource.getBackupTargetDataSources().get(dataSourceId); dynamicDataSource.addDataSource(key, dataSource); }}Copy the code
use
public void doExecute(ReqTestParams reqTestParams){
// Structure condition
Predicate<? superMap<String, Object>> predicate =......... ;// Switch data sources
dataSourceUtil.switchDataSource("testKey", predicate);
// Database operation
mapper.testQuery();
// Clear the cache to avoid memory leaks
DynamicDataSourceContextHolder.removeContextKey();
}
Copy the code
The removeContextKey method is called to clean up the cache every time the data source is used to avoid memory leaks. Consider using AOP to intercept specific methods and use post-notification to perform cache cleanup for the execution method proxy.
@Aspect
@Component
@Slf4j
public class RequestHandleMethodAspect {
@after (" xxxxxxxxxxxxxxxxexecution expression XXXXXXXXXXXXXXXXXX ")
public void afterRunning(JoinPoint joinPoint){
String name = joinPoint.getSignature().toString();
long id = Thread.currentThread().getId();
log.info(Start emptying current thread data source, thread ID :{}, proxy method :{},id,name);
DynamicDataSourceContextHolder.removeContextKey();
log.info("Current thread data source cleared, returned to default data source :{}",id); }}Copy the code
Characteristics analysis
(1) Parameterized switching data source mode, starting point and subcontracting mode are different, which data source can be determined only when it is suitable for running.
(2) Manually switch data sources;
(3) Mapper and XML paths can be defined freely without subcontracting;
(4) Add data source, do not need to modify the Java configuration class, just modify the data source configuration file.
Annotation way
thought
In this method, annotations and AOP are used to mark custom annotations for methods that need to switch data sources. The annotation attribute specifies the data source ID. Then, AOP aspects are used to intercept annotation marked methods and switch to the corresponding data source before the method is executed. At the end of method execution, switch to the default data source.
Note that the custom section needs to take precedence over the @Transactional annotation corresponding section.
Otherwise, the @Transactional section takes precedence when both custom annotations and @Transactional are used together. When the section calls the getConnection method, Go to call AbstractRoutingDataSource determineCurrentLookupKey method, get is the default of the master data source at this time. Then @ UsingDataSource corresponding key aspect even set up the current thread data sources, also won’t go back call determineCurrentLookupKey method to switch the data source.
Design ideas
Data source registration
Same as above.
Data source switching
All @usingdatasource annotation methods are intercepted using sections. According to the dataSourceId attribute, the corresponding data source is switched before the method is executed. At the end of method execution, clear the cache and switch to the default data source.
Code implementation
Data source configuration file
Same as above.
Defining dynamic data sources
Same as above.
Define data source key thread variable hold
Same as above.
Multi-source automatic configuration class
Same as above.
Data source switching utility class
Switch logic:
(1) Generate the data source key of the current thread
(3) Get the data source object from the data source cache pool according to the ID, and add it to the backupTargetDataSources cache pool again;
ThreadLocal sets the data source key corresponding to the current thread;
(5) before perform database operations, spring will call determineCurrentLookupKey method to obtain the key, and then according to the key to remove data source, data cache pool then getConnection access to the data source connection;
(6) Use the data source to perform database operations;
(7) Release cache: threadLocal cleans up data source information of current thread, data source cache pool cleans up data source key and data source object of current thread.
public static void switchDataSource(String dataSourceId) {
if (StringUtils.isBlank(dataSourceId)) {
throw new ClientException("Failed to switch data source, data source ID cannot be null");
}
try {
String threadDataSourceKey = UUID.randomUUID().toString();
DataSourceUtil.addDataSource(threadDataSourceKey, dataSourceId);
DynamicDataSourceContextHolder.setContextKey(threadDataSourceKey);
}
catch (Exception e) {
log.error("Failed to switch data source. The specified data source was not found. Make sure the specified data source ID is configured in the configuration file. dataSourceId:{}", dataSourceId);
throw new ClientException("Failed to switch data source. The specified data source was not found. Make sure the specified data source ID is configured in the configuration file. dataSourceId:"+ dataSourceId, e); }}Copy the code
Custom annotations
Custom annotations mark the data source used by the current method, default to the primary data source.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UsingDataSource {
String dataSourceId(a) default "master";
}
Copy the code
section
It mainly defines pre-notification and post-notification, intercepts methods of UsingDataSource annotation tag, switches data source before method execution, and clears data source cache after method execution.
The section that needs to be marked has a higher priority than the section corresponding to the @Transaction annotation. Otherwise, the @Transactional section takes precedence when both custom annotations and @Transactional are used together. When the section calls the getConnection method, Go to call AbstractRoutingDataSource determineCurrentLookupKey method, get is the default of the master data source at this time. Then @ UsingDataSource corresponding key aspect even set up the current thread data sources, also won’t go back call determineCurrentLookupKey method to switch the data source.
@Aspect
@Component
@Slf4j
@Order(value = 1)
public class DynamicDataSourceAspect {
// Intercepts the UsingDataSource annotation tag method, which switches the data source before execution
@Before(value = "@annotation(usingDataSource)")
public void before(JoinPoint joinPoint, UsingDataSource usingDataSource) {
String dataSourceId = usingDataSource.dataSourceId();
log.info("Start switching data source before executing target method, target method :{}, dataSourceId:{}", joinPoint.getSignature().toString(), dataSourceId);
try {
DataSourceUtil.switchDataSource(dataSourceId);
}
catch (Exception e) {
log.error("Failed to switch data source! Data source may not be configured or available, data source ID:{}", dataSourceId, e);
throw new ClientException("Failed to switch data source! Data source may not be configured or available, data source ID:" + dataSourceId, e);
}
log.info("Target method :{}, switched to data source :{}", joinPoint.getSignature().toString(), dataSourceId);
}
// Intercepts the UsingDataSource annotation method to clean up the data source after execution to prevent memory leaks
@After(value = "@annotation(com.hosjoy.hbp.dts.common.annotation.UsingDataSource)")
public void after(JoinPoint joinPoint) {
log.info("Target method completed, perform cleanup, switch to default data source, target method :{}", joinPoint.getSignature().toString());
try {
DynamicDataSourceContextHolder.removeContextKey();
}
catch (Exception e) {
log.error("Failed to clean data source", e);
throw new ClientException("Failed to clean data source", e);
}
log.info("Target method :{}, data source cleaned up, default data source returned", joinPoint.getSignature().toString()); }}Copy the code
use
@UsingDataSource(dataSourceId = "slave1")
@Transactional
public void test(a){
AddressPo po = new AddressPo();
po.setMemberCode("asldgjlk");
po.setName("lihe");
po.setPhone("13544986666");
po.setProvince("asdgjwlkgj");
addressMapper.insert(po);
int i = 1 / 0;
}
Copy the code
Dynamic addition (very useful)
Service Scenario Description
This is not a very common business scenario, but it has certainly happened to someone.
There is only one default data source configured in the project, and the runtime needs to dynamically add new data sources, not static multiple data sources configured. For example, you need to go to the server to read data source configuration information in real time (not configured locally) before performing database operations.
In this business scenario, the above three methods are not applicable, because the above data sources are formulated in yML files in advance.
Implementation approach
With the exception of step 6, this can be done using previously written code.
Idea is:
(1) Create new data sources;
(2) DynamicDataSource register new datasource;
(3) Switch: set the data source key of the current thread; Add a temporary data source;
(4) Database operations (must be implemented in another service, otherwise transactions cannot be controlled);
(5) Clear current thread data source key and temporary data source;
(6) Clear the data source just registered;
(7) The default data source has been returned.
code
The code is pretty crude, but that’s what the template looks like, basically expressing how the implementation is going to work.
Service A:
public String testUsingNewDataSource(a){
DynamicDataSource dynamicDataSource = RequestHandleMethodRegistry.getContext().getBean("dynamicDataSource", DynamicDataSource.class);
try {
// Simulate reading data source information from the server
/ /...
/ /...
// Create a new data source
HikariDataSource dataSource = (HikariDataSource) DataSourceBuilder.create().build();
dataSource.setJdbcUrl("JDBC: mysql: / / 192.168. XXX, XXX, XXXX/XXXXX? ...");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
//DynamicDataSource registers a new data source
dynamicDataSource.addDataSource("test_ds_id", dataSource);
// Set the current thread data source key and add a temporary data source
DataSourceUtil.switchDataSource("test_ds_id");
// Database operation (must be implemented in another service, otherwise transaction cannot be controlled)
serviceB.testInsert();
}
finally {
// Clear the current thread data source key
DynamicDataSourceContextHolder.removeContextKey();
// Clean up the data source you just registered
dynamicDataSource.removeDataSource("test_ds_id");
}
return "aa";
}
Copy the code
Service B:
@Transactional(rollbackFor = Exception.class)
public void testInsert(a) {
AddressPo po = new AddressPo();
po.setMemberCode("555555555");
po.setName(Li he, "");
po.setPhone("16651694996");
po.setProvince(Jiangsu Province);
po.setCity(Nanjing City);
po.setArea("Pukou District");
po.setAddress("219 Ningliu Road, Pukou District, Nanjing City");
po.setDef(false);
po.setCreateBy("23958");
addressMapper.insert(po);
// Test transaction rollback
int i = 1 / 0;
}
Copy the code
DynamicDataSource: Added the removeDataSource method to clean up registered new data sources.
public class DynamicDataSource extends AbstractRoutingDataSource {... . .public void removeDataSource(String key){
this.backupTargetDataSources.remove(key);
super.setTargetDataSources(this.backupTargetDataSources);
super.afterPropertiesSet(); }... . . }Copy the code
Four ways to compare
sub-contracting | Parameterized switching | Annotation way | Dynamic add mode | |
---|---|---|---|---|
Applicable scenario | When you code, you know which data source to use | The runtime determines which data source to use | When you code, you know which data source to use | The runtime dynamically adds new data sources |
Switch mode | automatic | manual | automatic | manual |
Mapper path | Need to subcontract | No request | No request | No request |
Whether the configuration class needs to be modified to add a data source | Need to be | Don’t need | Don’t need | \ |
Implementation complexity | simple | complex | complex | complex |
Transaction issues
Using the above data source configuration, transaction control for a single data source can be achieved.
For example, in a service method, you can implement transaction control for a single data source when you need to operate on multiple data sources to perform CUD. The method is as follows: the methods requiring transaction control are separately extracted to another service to realize transaction control of a single transaction method.
ServiceA:
public void updateMuilty(a){
serviceB.updateDb1();
serviceB.updateDb2();
}
Copy the code
ServiceB:
@UsingDataSource(dataSourceId = "slave1")
@Transactional
public void updateDb1(a){
// Service logic......
}
@UsingDataSource(dataSourceId = "slave2")
@Transactional
public void updateDb2(a){
// Service logic......
}
Copy the code
However, it is not so easy to control transactions of multiple data sources in the same method, which falls within the scope of distributed transactions. You can consider using the Atomikos open source project to implement JTA distributed transactions or Ali’s Fescar framework.
Since distributed transaction control is involved, the implementation is more complex, so this is just the introduction of this problem, and it will be patched up later in some time.
Refer to the article
1. www.liaoxuefeng.com/article/118… Spring master/slave database configuration and dynamic data source switching principle
2. blog.csdn.net/hekf2010/ar… Springcloud most libraries multi-data source integration, query dynamic switching database
3. blog.csdn.net/tuesdayma/a… Two integration methods of springboot- Mybatis multi-data sources