前言:本文主要通过AbstractRoutingDataSource,实现根据 http 访问携带的标识动态切换数据源;
1 AbstractRoutingDataSource 介绍:
AbstractRoutingDataSource 是 Spring 框架中的一个抽象类,它可以用来实现动态数据源切换。在多数据源场景下,AbstractRoutingDataSource 可以根据不同的请求来动态地选择合适的数据源进行操作,以达到高效利用多个数据源的目的。
AbstractRoutingDataSource 并不是直接连接数据库的数据源,它只是一个路由数据源,它负责根据一定的规则选择一个真正的数据源来执行数据操作。它的作用可以归纳为以下几点:
(1). 实现多数据源动态切换:AbstractRoutingDataSource 可以通过动态的选定数据源,达到多数据源操作的目的。特别在分布式环境中,可以根据业务需求将数据进行分片,然后将不同的分片数据存储在不同的数据库中,这样就能实现数据负载均衡和高可用性。
(2)… 封装数据库连接池和连接的获取逻辑:AbstractRoutingDataSource 通过封装多个数据源连接池的实现细节,屏蔽底层数据源的细节,使得业务代码不需要关心连接的获取和释放,从而简化了业务代码的编写。
(3). 实现数据源的动态切换:AbstractRoutingDataSource 可以通过动态切换数据源,实现数据源的动态切换,从而在不影响系统正常运行的情况下,能够对数据源进行升级、迁移等操作。
综上所述,AbstractRoutingDataSource 的主要作用是实现多数据源的动态切换,这在多个数据库之间实现负载均衡、数据迁移、分片存储等场景下,可以大大提高系统的性能和可用性。
2 springBoot 集成:
2.1 pom.xml 引入jar:
com.baomidou mybatis-plus-boot-starter 3.5.2 mysql mysql-connector-java 8.0.21
2.2 数据源解析类:
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(); ListdataSourceKeys = 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
DynamicDataSource 路由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 ThreadLocalthreadLocal = new ThreadLocal<>(); private List 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 getDataSourceKeys() { return dataSourceKeys; } public void setDataSourceKeys(List 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 拦截http 请求,设置本次访问的db:
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 ThreadLocalthreadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { // 不是 httpreuqest 请求直接放行 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 { // 在方法执行完毕或者执行报错后,移除数据源 if (null != threadLocal.get() && threadLocal.get()) { DynamicDataSource.clear(); } threadLocal.remove(); } }
WebConfiguration 拦截:
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; /** * 拦截器配置 * * @param registry 注册类 */ @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%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