动态数据源最佳实战

1:为什么要用动态数据源

最常见的场景就是单体架构系统需要跨库进行业务增删改查,例如一个购车系统可能需要查询用户信息,然后在进行购买汽车的逻辑。而用户表和汽车表可能不在一个数据库中。
如下图所示,可能一个购买汽车的下单流程为:

  1. 用户提交请求。
  2. 基于id到数据源1查询当前用户信息。
  3. 基于汽车id到数据源3查询汽车信息。
  4. 将用户id、汽车id、数量存到数据源2订单表中。
    在这里插入图片描述
    面对这种数据源,我们不妨了解一下spring数据源加载原理,寻找可以扩展的点从而完成多数据源切换完成该业务需求的开发。

2:了解spring数据源加载原理

我们在调试spring数据源的时候看到这么一个类AbstractRoutingDataSource的类,它的类图如下所示:
在这里插入图片描述
可以看到它用到了InitializingBean这个接口,说明在bean加载完成之后肯定有进行一些相关数据源的操作,我们不妨看看源码。

  1. 我们在AbstractRoutingDataSource看到这个方法的实现,如下所示,可以看到它的逻辑很简单:从targetDataSources获取到数据源的key的value存到resolvedDataSources,作为后续数据源切换时用到的材料。
  2. 如果resolvedDefaultDataSource 不为空,则将当前项目的defaultTargetDataSource
    设置为defaultTargetDataSource 。
@Override
 public void afterPropertiesSet() {
   
  if (this.targetDataSources == null) {
   
   throw new IllegalArgumentException("Property 'targetDataSources' is required");
  }
   
  //将targetDataSources的值存到resolvedDataSources中,作为后续切换的依据。
  this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
  this.targetDataSources.forEach((key, value) -> {
   
   Object lookupKey = resolveSpecifiedLookupKey(key);
   DataSource dataSource = resolveSpecifiedDataSource(value);
   this.resolvedDataSources.put(lookupKey, dataSource);
  });
//如果resolvedDefaultDataSource 不为空,则将当前项目的defaultTargetDataSource 设置为defaultTargetDataSource
  if (this.defaultTargetDataSource != null) {
   
   this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
  }
 }

了解spring boot启动时数据源的基本流程之后,我们再来了解一下这个加载的细节。

遍历targetDataSources时,对key和value解析流程
上面整体流程时提到,spring会从targetDataSources中取出数据源的key和value进行解析然后存放到resolvedDataSources,这里我们不妨看看实现细节。

首先是resolveSpecifiedLookupKey方法,源码如下,可以看到代码实现很简单,原原本本返回出去即可。

protected Object resolveSpecifiedLookupKey(Object lookupKey) {
   
  return lookupKey;
 }

再来看看resolveSpecifiedDataSource,逻辑也很简单,如果传进来的数据源配置是字符串类型,说明是配置中取到的,需要用dataSourceLookup转换成数据源类,如果本身就是DataSource类直接返回即可。

protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
   
  if (dataSource instanceof DataSource) {
   
   return (DataSource) dataSource;
  }
  else if (dataSource instanceof String) {
   
   return this.dataSourceLookup.getDataSource((String) dataSource);
  }
  else {
   
   throw new IllegalArgumentException(
     "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
  }
 }

spring何时决定使用哪个数据源
.这里我们不妨随便拿一段mybatis查询的业务代码来debug了解一下细节,以笔者为例笔者就在下面这段代码中插入一个断点。
在这里插入图片描述
然后在debug过程中走到了一个DataSourceUtils工具类,调用一个getConnection方法,可以看出这个操作就是和数据源相关的。
在这里插入图片描述
我们步入查看逻辑,于是我们的代码又来到了AbstractRoutingDataSource,可以看到一个determineTargetDataSource方法,我们猜想这个可能就和数据源切换有关系。
在这里插入图片描述
重点来了,笔者这里将代码贴出来,可以看到determineTargetDataSource的逻辑:

  • 获取当前web应用用到key。
  • 拿着key到上文启动时存放数据源键值对的resolvedDataSources获取数据源。
  • 返回数据源出去,然后代码会调用getConnection建立连接。
protected DataSource determineTargetDataSource() {
   
  Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
  Object lookupKey = determineCurrentLookupKey();
  DataSource dataSource = this.resolvedDataSources.get(lookupKey);
  if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
   
   dataSource = this.resolvedDefaultDataSource;
  }
  if (dataSource == null) {
   
   throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
  }
  return dataSource;
 }

我们查看代码细节,可以看到determineCurrentLookupKey是一个抽象类,默认情况下有个单数据源的实现类,所以如果我们希望动态切换数据源,完完全全可以继承这个类,然后实现动态数据源切换逻辑,从而实现spring多数据源动态切换,一个功能在多个数据源中查询的逻辑。

在这里插入图片描述

3:功能实现思路

从源码中了解了spring的设计思路之后,我们现在就不妨设计一下多数据源切换的实现思路。

首先是技术实现上:

  • maven引入相关依赖。
  • 编写多数据源的配置。
  • 编写配置类将数据源加载到spring容器中。
  • 编写一个线程数据源管理类,分别存放每一个请求线程的数据源key值。
  • 编写一个数据源管理类,负责加载项目运行时的数据源加载和存放。
  • 继承AbstractRoutingDataSource重写determineCurrentLookupKey基于线程数据源管理类实现获取最新数据源的逻辑。

业务实现上:

  • 编写用户信息查询功能。
  • 编写汽车信息查询功能。
  • 编写下单信息存储功能。

好了,话不多说,现在就开始实现这个需求。

2.1:基于spring boot实现多数据源配置步骤

引入相关依赖

首先我们创建好一个spring boot脚手架之后引入下面这些依赖,这步骤没有什么特殊的地方,读者按需复制即可。

<!--web模块依赖-->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!--spring核心aop模块依赖-->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>
  <!--德鲁伊数据源连接池依赖-->
  <dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.2.8</version>
  </dependency>
  <!--mybatis依赖-->
  <dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>2.2.2</version>
  </dependency>
  <!--mysql驱动-->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.49</version>
  </dependency>
  <!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值