AbstractRoutingDataSource动态数据源

3,026 阅读4分钟

前言:在项目中需要利用多数据源做分库,所以就简单的看了下AbstractRoutingDataSource的原理,做个学习笔记,当然也很希望这篇笔记能给有需要的小伙伴带来帮助,共同学习。

总结:spring提供的一个多数据源解决方案就是抽象类AbstractRoutingDataSource,我们可以利用AbstractRoutingDataSource实现数据库分片,读写分离等功能。接下来就让我们看看AbstractRoutingDataSource是怎么支持多数据源切换的。

AbstractRoutingDataSource继承自AbstractDataSource,AbstractDataSource实现了DataSource接口。我们可以认为AbstractRoutingDataSource是一批DataSource实例的集合体,当需要操作数据库的时候肯定是需要先从数据库连接池获取可用的Connection。AbstractRoutingDataSource通过实现DataSource接口的getConnection方法来选择操作那个数据源。具体原理就是这样的,接下来我们看源码。

1.通过determineTargetDataSource()去获取connection

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

2.determineTargetDataSource方法很简单就是通过一个lookupKey去 private Map<Object, DataSource> resolvedDataSources;这个map中找到对应的数据源,如果在map中没有找到数据源就用默认数据源resolvedDefaultDataSource。

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;
}

3.那么接下来的问题就是lookupKey这值怎么得到,AbstractRoutingDataSource中有个抽象方法需要怎么自己去实现取数据源的规则。我们可以根据自己业务需求实现该抽象方法。

protected abstract Object determineCurrentLookupKey();

4.那么接下来我们应该想数据源是怎么放入到resolvedDataSources这个map中的,肯定是先放进去我们需要的时候才可以取的到。AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean,我们从AbstractRoutingDataSource的源码可以看到它实现了InitializingBean接口,InitializingBean接口是org.springframework.beans.factory包下的一个接口,当我们实例化一个bean的时候会自动调用该接口的afterPropertiesSet()方法,AbstractRoutingDataSource对afterPropertiesSet方法的实现如下:

@Nullable
private Map<Object, Object> targetDataSources;

@Nullable
private Object defaultTargetDataSource;

@Nullable
private Map<Object, DataSource> resolvedDataSources;

@Nullable
private DataSource resolvedDefaultDataSource;

@Override
public void afterPropertiesSet() {
   if (this.targetDataSources == null) {
      throw new IllegalArgumentException("Property 'targetDataSources' is required");
   }
   this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
   this.targetDataSources.forEach((key, value) -> {
      Object lookupKey = resolveSpecifiedLookupKey(key);
      DataSource dataSource = resolveSpecifiedDataSource(value);
      this.resolvedDataSources.put(lookupKey, dataSource);
   });
   if (this.defaultTargetDataSource != null) {
      this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
   }
}

5.看完上面的代码我们知道afterPropertiesSet()把targetDataSources中的DataSource做了加工和校验之后放入了resolvedDataSources,同理defaultTargetDataSource也赋值给了resolvedDefaultDataSource,所以我们只要将数据源集合和默认数据源通过AbstractRoutingDataSource提供的set方法放入targetDataSources和defaultTargetDataSource就可以了。

public void setTargetDataSources(Map<Object, Object> targetDataSources) {
   this.targetDataSources = targetDataSources;
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
   this.defaultTargetDataSource = defaultTargetDataSource;
}

6.到这里AbstractRoutingDataSource动态数据源的原理我们已经基本了解了,但是在网上看到有人说当我们开启了事务之后,是无法在去切换数据源的,其实这个问题是不存在的。为什么不存在呢?先看下面这段话:

事务的四个基本要素(ACID)中第一就是原子性,我们可以理解事务是数据库操作的最小单元,其实事务是一直存在的
并不是我们认为的开启事务之后事务才会生效。事务默认是自动提交,我们每次执行完一个sql就相当一个事务自动提交
了,我们在spring中开启事务其实就是把connection的事务提交方法改为了手动的了(java api手册原话- 如果连接
处于自动提交模式,则其所有SQL语句将作为单个事务执行并提交。 否则,它的SQL语句被分组成通过调用方法commit
或方法rollback 。 默认情况下,新连接处于自动提交模式。)看完了这段话如果你理解了那么答案就出来了

答案:因为数据库的事务是通过connection对象的setAutoComit方法设置手动提交,然后根据操作情况commit或者rollback。如果setAutoComit不手动设置那么就是默认自动提交,每次数据库操作都是一个个的事务,会从连接池获取新的connection,操作完成将connection还回连接池,但是如果setAutoComit设置为手动提交,则在事务开始的时候获取数据库连接并将connection缓存到该事务线程的ThreadLocal中,后面的数据库操作,都用同一个connection,事务结束后将connection归还给连接池,所以给我们的感觉是开启了事务数据源切换失效了,总结一下就是在一个事务操作中是不会两次获取connection的,也就没办法触发数据源切换了。

......................................................................................

好好学习,天天向上!!!