浅析动态切换数据源的原理(接上篇)

baojunzh
发布于 2022-10-31 16:27
浏览
0收藏

上一篇我们实现了多数据源动态切换的功能,这次我们来看一下是如何实现的。

没看过上一篇的点击这里


我们在DynamicDataSourceConfig中配置了所有数据源信息,并存在一个map中

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区


浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区


因为我们的DynamicDataSource继承了AbstractRoutingDataSource抽象类,AbstractRoutingDataSource中有几个重要的属性在这里我们用到了


@Nullable
private Map<Object, Object> targetDataSources;
@Nullable
private Object defaultTargetDataSource;

  • targetDataSources:持有我们多数据源的所有数据源,key为不重复的对象用于唯一标识一个数据源,value为数据源DataSource实例
  • defaultTargetDataSource:默认的数据源实例


可以看到我们在配置类中new DynamicDataSource在构造方法中传了两个参数,就对应了defaultTargetDataSourcetargetDataSources,这样DynamicDataSource就持有了我们的默认数据源和所有数据源。


然后我们可以看到DynamicDataSource实现了AbstractRoutingDataSource抽象类的抽象方法determineCurrentLookupKey()

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区

这个方法的作用就是返回一个key,该key对应当前你希望使用的DataSource。在这个里,我们优化了一下,使用一个类DataSourceHolder持有我们的这个key,所以DynamicDataSource方法就变得很简洁了,就是在determineCurrentLookupKey方法中调用DataSourceHolder的静态方法getDataSourceName获取到当前用户想要使用的DataSourcekey


AbstractRoutingDataSourcedetermineTargetDataSource方法中调用了determineCurrentLookupKey方法:

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区



我们可以看到拿到key后,是从resolvedDataSources(一个map对象)中get对应的DataSource,get不到返回resolvedDefaultDataSource,看样子是默认数据源


@Nullable
private Map<Object, DataSource> resolvedDataSources;


但是resolvedDataSourcesresolvedDefaultDataSource都不是刚刚我们所介绍的targetDataSourcesdefaultTargetDataSource


那是因为做了赋值操作,

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#afterPropertiesSet

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区


AbstractRoutingDataSource实现了InitializingBean接口的afterPropertiesSet,会在实例化bean后调用此方法(AbstractAutowireCapableBeanFactory#invokeInitMethods方法调用的),所以resolvedDataSourcesresolvedDefaultDataSource就有值了。


继续回到determineTargetDataSource方法,该返回DataSource实例,既然是返回实例,肯定是有地方去获取DataSource,因为我们用的持久层框架是mybatis,所以获取DataSource的这个操作势必是mybatis发起的。


清楚了这一点就好办了,那就要看mybatis了,查阅资料我们可以发现,mybatis包中有这样一个类负责管理JDBC连接的生命周期,它就是org.mybatis.spring.transaction.SpringManagedTransactionmybatis-spring.jarspring集成mybatis的jar包)它实现了mybatis中的Transaction接口

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区


我们可以看到接口中有一个getConnection()方法,即是要从数据库连接池中获取一个连接:


@Override
public Connection getConnection() throws{
  if (this.connection == null) {
    openConnection();
  }
  return this.connection;
}


在连接为空时,调用openConnection()方法获取连接,我们看看openConnection方法做了什么操作:

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区


该方法第一个调用DataSourceUtils.getConnection(this.dataSource)获取连接,其调用DataSourceUtils中的doGetConnection(dataSource)获取连接

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区


然后在这个方法内部又调用fetchConnection(dataSource)方法获取连接

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区


fetchConnection(datasource)内直接就调用了DataSource对象的getConnection方法

浅析动态切换数据源的原理(接上篇)-鸿蒙开发者社区


因为我们的DynamicDataSource没有重写getConnection方法,那么就会调父类的


@Override
public Connection getConnection() throws{
   return determineTargetDataSource().getConnection();
}


可以看到先调了determineTargetDataSource方法获取DataSourcegetConnection()determineTargetDataSource方法刚刚我们已经看过了。


总结


大致思想就是,mybatis执行SQL时会从DataSource拿一个JDBC连接,所以spring巧妙的利用了这个特点,它提前拿到多个数据源的实例信息,在mybatis获取连接时根据用户的指令,动态的选择返回哪个连接,这些操作对mybatis是透明的。


spring把一些通用方法高度抽象到AbstractRoutingDataSource抽象类中,使用两个变量管理数据源,targetDataSourcesdefaultTargetDataSource,然后再预留出determineCurrentLookupKey方法供我们实现,即只需要返回一个key就可以,返回的key用于从targetDataSources中选取出我们指定的数据源。


所以,除了determineCurrentLookupKey方法外,其他的操作对于使用者来说也是透明的,使用者只需要关心如何在determineCurrentLookupKey方法中实现自己的选择数据源的规则即可。


只不过,我们是通过AOP切面拦截的方式增强方法,在持久层方法执行前即在mybatis执行SQL前我们事先把DataSource换成我们指定的即可。


在这里我们换数据源的方式是通过一个DataSourceHolder类中的ThreadLocal实现的,原因是为了保证多线程并发环境下不同线程切换数据源时不会乱,Threadlocal线程独有的一个对象,在其内部保存我们的key,在determineCurrentLookupKey中获取并返回即可。


代码已上传到码云,springboot版本和SSM都有

感兴趣的可以去下载

​​​​https://gitee.com/itwalking/springboot-dynamic-datasource​​​

​​​​https://gitee.com/itwalking/ssm-dynamic-datasource​


本文转载自公众号BiggerBoy

分类
标签
已于2022-10-31 16:27:08修改
收藏
回复
举报
回复
    相关推荐