您现在的位置是:网站首页> 内容页

Spring多数据源、动态数据源源码解析

  • 大奖888手机版客户端
  • 2019-01-02
  • 275人已阅读
简介在Java中所有的连接池都按照规范实现DataSource接口,在获取连接的时候即可通过getConnection()获取连接而不用关系底层究竟是何数据库连接池。1publicin

在Java中所有的连接池都按照规范实现DataSource接口,在获取连接的时候即可通过getConnection()获取连接而不用关系底层究竟是何数据库连接池。

1 public interface DataSource extends CommonDataSource Wrapper {2 3 Connection getConnection() throws SQLException4 5 Connection getConnection(String username String password) throws SQLException6 }

 

在大多数系统中我们只需要一个数据源,而现在WEB系统通常是Spring为基石。不管你是xml配置,javaBean配置还是ymlproperties配置文件配置,其核心就是注入一个数据源交给spring的进行管理。

而在部分系统中我们可能会面临一些情况,连接多个表,主从,甚至多个不同的库等等情况,核心需求就是我们可能需要配置多个连接池。

在mybatis系统中我们使用多数据源可以配置配置多个DataSourceSqlSessionFactorySqlSessionTemplate,然后在xml和mapper也分开管理。具体可以参考https://blog.csdn.net/neosmith/article/details/61202084 这篇博客。

这种方案在小的系统足够使用,作者认为更适合于多个不同的数据库。

回归正题,在Spring中从2.0.1版本默认提供了AbstractRoutingDataSource,我们继承它实现相关方法,把所有需要的数据源设置进去即可动态的切换数据源。我们可以看下核心方法的源码。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {​ //设置所有的数据源 private Map<Object Object> targetDataSources //设置默认的数据源,在没有找到相关数据源的时候会返回默认数据源 private Object defaultTargetDataSource //快速失败,可忽略 private boolean lenientFallback = true //Jndi相关,可忽略 private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup() //经过解析后的所有数据源,核心 private Map<Object DataSource> resolvedDataSources //经过解析后的默认数据源,核心 private DataSource resolvedDefaultDataSource​ //设置相关参数方法 public void setTargetDataSources(Map<Object Object> targetDataSources) { this.targetDataSources = targetDataSources } public void setDefaultTargetDataSource(Object defaultTargetDataSource) { this.defaultTargetDataSource = defaultTargetDataSource } public void setLenientFallback(boolean lenientFallback) { this.lenientFallback = lenientFallback } public void setDataSourceLookup(DataSourceLookup dataSourceLookup) { this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup()) }​ @Override public void afterPropertiesSet() { //检测是否设置所有的数据源 if (this.targetDataSources == null) { throw new IllegalArgumentException("Property "targetDataSources" is required") } //解析所有数据源,一般没什么用,主要是如果Map<Object Object> targetDataSources的value是string则会从Jndi数据源查找 this.resolvedDataSources = new HashMap<Object DataSource>(this.targetDataSources.size()) for (Map.Entry<Object Object> entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()) DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()) this.resolvedDataSources.put(lookupKey dataSource) } //同上解析默认数据源 if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource) } }​ @Override public Connection getConnection() throws SQLException { //核心,获取数据源先查找当前连接池再获取数据源 return determineTargetDataSource().getConnection() }​ @Override public Connection getConnection(String username String password) throws SQLException { return determineTargetDataSource().getConnection(username password) }​ protected DataSource determineTargetDataSource() { //调用determineCurrentLookupKey,然后去resolvedDefaultDataSource查找,有就返回对应数据源,没有返回默认数据源 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需要我们自己去实现。 //通常需要结合Aop和ThreadLocal。我们Aop从注解上获取当前用户用户所希望的数据源,然后设置到当前线程。在determineCurrentLookupKey再从当前线程拿出来返回给determineTargetDataSource由其决定最终数据源 protected abstract Object determineCurrentLookupKey()​}

  

以上具体的实现可以参考这篇博客 https://blog.csdn.net/u012881904/article/details/77449710 需要注意的是AOP的order必须在事物的order之前。

优点:方便配置,便捷使用。缺点:默认实现有一定局限性,大多数人足够使用。如果你有更复杂的使用场景,多库数据源,分组数据源,多主多从等等较复杂场景可以尝试

https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter

一个基于springboot的快速集成多数据源的启动器。

一个标准的主从的配置如下,引入相关配置即可使用。更多使用的方式查看相关文档。

spring: datasource: dynamic: primary: master #设置默认的数据源或者数据源组默认值即为master如果你主从默认下主库的名称就是master可不定义此项。 datasource: master: username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://47.100.20.186:3306/dynamic?characterEncoding=utf8&useSSL=false slave_1: username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://47.100.20.186:3307/dynamic?characterEncoding=utf8&useSSL=false slave_2: username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://47.100.20.186:3308/dynamic?characterEncoding=utf8&useSSL=false

  

实现核心源码如下

public class DynamicRoutingDataSource extends AbstractRoutingDataSource { /** * 所有库 */ private Map<String DataSource> dataSourceMap /** * 分组数据库 */ private Map<String DynamicGroupDatasource> groupDataSources = new HashMap<>() @Setter private DynamicDataSourceProvider dynamicDataSourceProvider @Setter private Class<? extends DynamicDataSourceStrategy> dynamicDataSourceStrategyClass /** * 默认数据源名称,默认master,可为组数据源名,可为单数据源名 */ @Setter private String primary @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceLookupKey() } @Override protected DataSource determineTargetDataSource() { String lookupKey = (String) determineCurrentLookupKey() if (groupDataSources.containsKey(lookupKey)) { log.debug("从 {} 组数据源中返回数据源" lookupKey) return groupDataSources.get(lookupKey).determineDataSource() } else if (dataSourceMap.containsKey(lookupKey)) { log.debug("从 {} 单数据源中返回数据源" lookupKey) return dataSourceMap.get(lookupKey) } log.debug("从默认数据源中返回数据") return groupDataSources.containsKey(primary) ? groupDataSources.get(lookupKey).determineDataSource() : dataSourceMap.get(primary) } @Override public void afterPropertiesSet() { this.dataSourceMap = dynamicDataSourceProvider.loadDataSources() log.debug("共加载 {} 个数据源" dataSourceMap.size()) //分组数据源 for (Map.Entry<String DataSource> dsItem : dataSourceMap.entrySet()) { String dsName = dsItem.getKey() if (dsName.contains("_")) { String[] groupDs = dsName.split("_") String groupName = groupDs[0] DataSource dataSource = dsItem.getValue() if (groupDataSources.containsKey(groupName)) { groupDataSources.get(groupName).addDatasource(dataSource) } else { try { DynamicGroupDatasource groupDatasource = new DynamicGroupDatasource(groupName dynamicDataSourceStrategyClass.newInstance()) groupDatasource.addDatasource(dataSource) groupDataSources.put(groupName groupDatasource) } catch (Exception e) { e.printStackTrace() } } } } //检测组数据源设置 Iterator<Map.Entry<String DynamicGroupDatasource>> groupIterator = groupDataSources.entrySet().iterator() while (groupIterator.hasNext()) { Map.Entry<String DynamicGroupDatasource> item = groupIterator.next() log.debug("组 {} 下有 {} 个数据源" item.getKey() item.getValue().size()) if (item.getValue().size() == 1) { log.warn("请注意不要设置一个只有一个数据源的组,{} 组将被移除" item.getKey()) groupIterator.remove() } } //检测默认数据源设置 if (groupDataSources.containsKey(primary)) { log.debug("当前的默认数据源是组数据源组名为 {} ,其下有 {} 个数据源" primary groupDataSources.size()) } else if (dataSourceMap.containsKey(primary)) { log.debug("当前的默认数据源是单数据源,数据源名为{}" primary) } else { throw new RuntimeException("请检查primary默认数据库设置,当前未找到" + primary + "数据源") } }}

  

文章评论

Top