Preface: This article mainly uses AbstractRoutingDataSource to dynamically switch data sources according to the identifier carried in http access;
1 Introduction to AbstractRoutingDataSource:
AbstractRoutingDataSource is an abstract class in the Spring framework, which can be used to implement dynamic data source switching. In the multi-data source scenario, AbstractRoutingDataSource can dynamically select the appropriate data source for operation according to different requests, so as to achieve the purpose of efficiently utilizing multiple data sources.
AbstractRoutingDataSource is not a data source directly connected to the database, it is just a routing data source, which is responsible for selecting a real data source to perform data operations according to certain rules. Its functions can be summarized as follows:
(1). Realize dynamic switching of multiple data sources: AbstractRoutingDataSource can achieve the purpose of multi-data source operation by dynamically selecting data sources. Especially in a distributed environment, data can be fragmented according to business needs, and then different fragmented data can be stored in different databases, so that data load balancing and high availability can be achieved.
(2)… Encapsulate the database connection pool and connection acquisition logic: AbstractRoutingDataSource encapsulates the implementation details of multiple data source connection pools and shields the details of the underlying data source, so that business code does not need to care about connection acquisition and release, thus simplifying the business Code writing.
(3). Realize dynamic switching of data sources: AbstractRoutingDataSource can realize dynamic switching of data sources by dynamically switching data sources, so that the data source can be upgraded, migrated, etc. without affecting the normal operation of the system.
To sum up, the main function of AbstractRoutingDataSource is to realize the dynamic switching of multiple data sources, which can greatly improve the performance and availability of the system in scenarios such as load balancing, data migration, and fragmented storage among multiple databases.
2 springBoot integration:
2.1 pom.xml import jar:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency>
2.2 Data source parsing class:
DataSourceConfig:
import com.example.dynamicdemo.config.DynamicDataSource; import com.zaxxer.hikari.HikariDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import javax.sql.DataSource; import java.util.*; /** * @author du_imba */ @Configuration public class DataSourceConfig {<!-- --> private static final Logger logger = LoggerFactory. getLogger(DataSourceConfig. class); @Autowired private Environment environment; private static final String SEP = ","; @Bean public DataSource getDynamicDataSource() {<!-- --> DynamicDataSource routingDataSource = new DynamicDataSource(); List<String> dataSourceKeys = new ArrayList<>(); Iterable sources = ConfigurationPropertySources. get(environment); Binder binder = new Binder(sources); BindResult bindResult = binder.bind("datasource.tinyid", Properties.class); Properties properties= (Properties) bindResult. get(); String names = properties. getProperty("names"); String dataSourceType = properties. getProperty("type"); // RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "datasource.tinyid."); // String names = propertyResolver. getProperty("names"); // String dataSourceType = propertyResolver. getProperty("type"); Map<Object, Object> targetDataSources = new HashMap<>(4); routingDataSource.setTargetDataSources(targetDataSources); routingDataSource.setDataSourceKeys(dataSourceKeys); // multiple data sources for (String name : names. split(SEP)) {<!-- --> Map<String, Object> dsMap = getSubProperties(name + ".",properties); DataSource dataSource = buildDataSource(dataSourceType, dsMap); buildDataSourceProperties(dataSource, dsMap); targetDataSources. put(name, dataSource); dataSourceKeys.add(name); } // Set the default data source routingDataSource.setDefaultTargetDataSource(targetDataSources.get("primary")); return routingDataSource; } private Map<String, Object> getSubProperties(String s,Properties properties) {<!-- --> Map<String, Object> dsMap = new HashMap<>(1<<2); dsMap.put("driver-class-name",properties.get(s + "driver-class-name")); dsMap.put("url",properties.get(s + "url")); dsMap.put("username",properties.get(s + "username")); dsMap.put("password",properties.get(s + "password")); return dsMap; } private void buildDataSourceProperties(DataSource dataSource, Map<String, Object> dsMap) {<!-- --> try {<!-- --> // This method has poor performance, use with caution BeanUtils.copyProperties(dataSource, dsMap); } catch (Exception e) {<!-- --> logger. error("error copy properties", e); } } private HikariDataSource buildDataSource(String dataSourceType, Map<String, Object> dsMap) {<!-- --> try {<!-- --> // String className = DEFAULT_DATASOURCE_TYPE; // if (dataSourceType != null & amp; & amp; !"".equals(dataSourceType.trim())) {<!-- --> // className = dataSourceType; // } // Class<? extends DataSource> type = (Class<? extends DataSource>) Class. forName(className); String driverClassName = dsMap.get("driver-class-name").toString(); String url = dsMap. get("url"). toString(); String username = dsMap.get("username").toString(); String password = dsMap. get("password"). toString(); return DataSourceBuilder. create() .driverClassName(driverClassName) .url(url) .username(username) .password(password) // .type(type) .type(HikariDataSource.class) .build(); } catch (Exception e) {<!-- --> logger.error("buildDataSource error", e); throw new IllegalStateException(e); } } }
DynamicDataSource routing db:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import java.util.List; import java.util.Random; /** * @author du_imba */ public class DynamicDataSource extends AbstractRoutingDataSource {<!-- --> private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); private List<String> dataSourceKeys; @Override protected Object determineCurrentLookupKey() {<!-- --> // if(dataSourceKeys. size() == 1) {<!-- --> // return dataSourceKeys. get(0); // } // Random r = new Random(); // return dataSourceKeys.get(r.nextInt(dataSourceKeys.size())); return getDb(); } public List<String> getDataSourceKeys() {<!-- --> return dataSourceKeys; } public void setDataSourceKeys(List<String> dataSourceKeys) {<!-- --> this.dataSourceKeys = dataSourceKeys; } public static void setDb(String db){<!-- --> threadLocal.set(db); } public static String getDb(){<!-- --> return threadLocal. get(); } public static void clear() {<!-- --> threadLocal. remove(); } }
2.3 Intercept http requests and set the db for this visit:
HttpRequestDynamic:
import lombok.extern.slf4j.Slf4j; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Slf4j public class HttpRequestDynamic implements HandlerInterceptor {<!-- --> final static ThreadLocal<Boolean> threadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {<!-- --> if (!(handler instanceof HandlerMethod)) {<!-- --> // It is not a httpreuqest request to be released directly return true; } DynamicDataSource.setDb(request.getHeader("db")); threadLocal. set(true); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {<!-- --> // After the method is executed or an error is reported, remove the data source if (null != threadLocal.get() & amp; & amp; threadLocal.get()) {<!-- --> DynamicDataSource.clear(); } threadLocal. remove(); } }
WebConfiguration interception:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @Import({<!-- -->HttpRequestDynamic.class}) public class WebConfiguration implements WebMvcConfigurer {<!-- --> @Autowired private HttpRequestDynamic httpRequestDynamic; /** * Interceptor configuration * * @param registry registration class */ @Override public void addInterceptors(InterceptorRegistry registry) {<!-- --> registry.addInterceptor(httpRequestDynamic).addPathPatterns("/**") .excludePathPatterns( "/file/get/*", "/image/view/*", "/**/error" ); } }
2.4 application.properties
server.port=9999 server.context-path=/tinyid batch.size.max=100000 #datasource.tinyid.names=primary datasource.tinyid.names=primary,secondary datasource.tinyid.primary.driver-class-name=com.mysql.cj.jdbc.Driver datasource.tinyid.primary.url=jdbc:mysql://localhost:3406/d1?useUnicode=true &characterEncoding=utf8 &zeroDateTimeBehavior=convertToNull &useSSL=true &serverTimezone=GMT+8 datasource.tinyid.primary.username=root datasource.tinyid.primary.password=ddsoft #datasource.tinyid.primary.maxActive=10 datasource.tinyid.secondary.driver-class-name=com.mysql.cj.jdbc.Driver datasource.tinyid.secondary.url=jdbc:mysql://localhost:3406/d2?useUnicode=true &characterEncoding=utf8 &zeroDateTimeBehavior=convertToNull &useSSL=true &serverTimezone=GMT+8 datasource.tinyid.secondary.username=root datasource.tinyid.secondary.password=ddsoft #datasource.tinyid.secondary.testOnBorrow=false #datasource.tinyid.secondary.maxActive=10
2.5 Request: put the header into this db
3 Summary:
This article mainly intercepts the http request, analyzes the db to be accessed this time, and then sets the db to the ThreadLocal of DynamicDataSource, so that the corresponding db connection can be obtained when accessing the database to complete the operation;
Git address reference: https://codeup.aliyun.com/61cd21816112fe9819da8d9c/dynamic-demo.git