前言:本文主要通过AbstractRoutingDataSource,实现根据 http 访问携带的标识动态切换数据源;
1 AbstractRoutingDataSource 介绍:
AbstractRoutingDataSource 是 Spring 框架中的一个抽象类,它可以用来实现动态数据源切换。在多数据源场景下,AbstractRoutingDataSource 可以根据不同的请求来动态地选择合适的数据源进行操作,以达到高效利用多个数据源的目的。
AbstractRoutingDataSource 并不是直接连接数据库的数据源,它只是一个路由数据源,它负责根据一定的规则选择一个真正的数据源来执行数据操作。它的作用可以归纳为以下几点:
(1). 实现多数据源动态切换:AbstractRoutingDataSource 可以通过动态的选定数据源,达到多数据源操作的目的。特别在分布式环境中,可以根据业务需求将数据进行分片,然后将不同的分片数据存储在不同的数据库中,这样就能实现数据负载均衡和高可用性。
(2)… 封装数据库连接池和连接的获取逻辑:AbstractRoutingDataSource 通过封装多个数据源连接池的实现细节,屏蔽底层数据源的细节,使得业务代码不需要关心连接的获取和释放,从而简化了业务代码的编写。
(3). 实现数据源的动态切换:AbstractRoutingDataSource 可以通过动态切换数据源,实现数据源的动态切换,从而在不影响系统正常运行的情况下,能够对数据源进行升级、迁移等操作。
综上所述,AbstractRoutingDataSource 的主要作用是实现多数据源的动态切换,这在多个数据库之间实现负载均衡、数据迁移、分片存储等场景下,可以大大提高系统的性能和可用性。
2 springBoot 集成:
2.1 pom.xml 引入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 数据源解析类:
DataSourceConfig:
importcom.example.dynamicdemo.config.DynamicDataSource;importcom.zaxxer.hikari.HikariDataSource;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.BeanUtils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.context.properties.bind.BindResult;importorg.springframework.boot.context.properties.bind.Binder;importorg.springframework.boot.context.properties.source.ConfigurationPropertySources;importorg.springframework.boot.jdbc.DataSourceBuilder;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.env.Environment;importjavax.sql.DataSource;importjava.util.*;/**
* @author du_imba
*/@ConfigurationpublicclassDataSourceConfig{privatestaticfinalLogger logger =LoggerFactory.getLogger(DataSourceConfig.class);@AutowiredprivateEnvironment environment;privatestaticfinalString SEP =",";@BeanpublicDataSourcegetDynamicDataSource(){DynamicDataSource routingDataSource =newDynamicDataSource();List<String> dataSourceKeys =newArrayList<>();Iterable sources =ConfigurationPropertySources.get(environment);Binder binder =newBinder(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 =newHashMap<>(4);
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDataSourceKeys(dataSourceKeys);// 多个数据源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);}// 设置默认数据源
routingDataSource.setDefaultTargetDataSource(targetDataSources.get("primary"));return routingDataSource;}privateMap<String,Object>getSubProperties(String s,Properties properties){Map<String,Object> dsMap =newHashMap<>(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;}privatevoidbuildDataSourceProperties(DataSource dataSource,Map<String,Object> dsMap){try{// 此方法性能差,慎用BeanUtils.copyProperties(dataSource, dsMap);}catch(Exception e){
logger.error("error copy properties", e);}}privateHikariDataSourcebuildDataSource(String dataSourceType,Map<String,Object> dsMap){try{// String className = DEFAULT_DATASOURCE_TYPE;// if (dataSourceType != null && !"".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();returnDataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password)// .type(type).type(HikariDataSource.class).build();}catch(Exception e){
logger.error("buildDataSource error", e);thrownewIllegalStateException(e);}}}
DynamicDataSource 路由db:
importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;importjava.util.List;importjava.util.Random;/**
* @author du_imba
*/publicclassDynamicDataSourceextendsAbstractRoutingDataSource{privatestaticfinalThreadLocal<String> threadLocal =newThreadLocal<>();privateList<String> dataSourceKeys;@OverrideprotectedObjectdetermineCurrentLookupKey(){// if(dataSourceKeys.size() == 1) {// return dataSourceKeys.get(0);// }// Random r = new Random();// return dataSourceKeys.get(r.nextInt(dataSourceKeys.size()));returngetDb();}publicList<String>getDataSourceKeys(){return dataSourceKeys;}publicvoidsetDataSourceKeys(List<String> dataSourceKeys){this.dataSourceKeys = dataSourceKeys;}publicstaticvoidsetDb(String db){
threadLocal.set(db);}publicstaticStringgetDb(){return threadLocal.get();}publicstaticvoidclear(){
threadLocal.remove();}}
2.3 拦截http 请求,设置本次访问的db:
HttpRequestDynamic:
importlombok.extern.slf4j.Slf4j;importorg.springframework.web.method.HandlerMethod;importorg.springframework.web.servlet.HandlerInterceptor;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;@Slf4jpublicclassHttpRequestDynamicimplementsHandlerInterceptor{finalstaticThreadLocal<Boolean> threadLocal =newThreadLocal<>();@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{if(!(handler instanceofHandlerMethod)){// 不是 httpreuqest 请求直接放行returntrue;}DynamicDataSource.setDb(request.getHeader("db"));
threadLocal.set(true);returntrue;}@OverridepublicvoidafterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex)throwsException{// 在方法执行完毕或者执行报错后,移除数据源if(null!= threadLocal.get()&& threadLocal.get()){DynamicDataSource.clear();}
threadLocal.remove();}}
WebConfiguration 拦截:
importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Import;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration@Import({HttpRequestDynamic.class})publicclassWebConfigurationimplementsWebMvcConfigurer{@AutowiredprivateHttpRequestDynamic httpRequestDynamic;/**
* 拦截器配置
*
* @param registry 注册类
*/@OverridepublicvoidaddInterceptors(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%2B8
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%2B8
datasource.tinyid.secondary.username=root
datasource.tinyid.secondary.password=ddsoft
#datasource.tinyid.secondary.testOnBorrow=false
#datasource.tinyid.secondary.maxActive=10
2.5 请求:header 头放入本次的db
3 总结:
本文主要通过拦截http 请求,解析本次要访问的db,然后将db 设置到DynamicDataSource的ThreadLocal 中,使得访问数据库时获取到对应的db连接完成操作;
git 地址参考:https://codeup.aliyun.com/61cd21816112fe9819da8d9c/dynamic-demo.git
版权归原作者 拽着尾巴的鱼儿 所有, 如有侵权,请联系我们删除。