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

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



1 public interface DataSource  extends CommonDataSource, Wrapper {
2
3   Connection getConnection() throws SQLException;
4
5   Connection getConnection(String username, String password) throws SQLException;
6 }

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

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

在mybatis系统中我们使用多数据源可以配置配置多个DataSource,SqlSessionFactory,SqlSessionTemplate,然后在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 + "数据源");
        }
    }

}

  

原文地址:https://www.cnblogs.com/taoyu-cd/p/9463955.html

时间: 2024-10-12 02:44:31

Spring多数据源、动态数据源源码解析的相关文章

【mybatis】mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)

一.概述 二.创建 mybatis数据源的创建过程稍微有些曲折. 1. 数据源的创建过程: 2. mybatis支持哪些数据源,也就是dataSource标签的type属性可以写哪些合法的参数? 弄清楚这些问题,对mybatis的整个解析流程就清楚了,同理可以应用于任何一个配置上的解析上. 从SqlSessionFactoryBuilder开始追溯DataSource的创建.SqlSessionFactoryBuilder中9个构造方法,其中字符流4个构造方法一一对应字节流4个构造方法,都是将m

基于spring多数据源动态调用及其事务处理

需求: 有些时候,我们需要连接多个数据库,但是,在方法调用前并不知道到底是调用哪个.即同时保持多个数据库的连接,在方法中根据传入的参数来确定. 下图的单数据源的调用和多数据源动态调用的流程,可以看出在Dao层中需要有一个DataSource选择器,来确定到底是调用哪个数据源. 实现方式 对Dao层提供一个公共父类,保持有多个数据源的连接(本人是基于iBatis,即保持多个SQLSessionTemplate) /** * Created by hzlizhou on 2017/2/6. */ p

java动态代理源码解析

众所周知,java动态代理同反射原理一直是许多框架的底层实现,之前一直没有时间来分析动态代理的底层源码,现结合源码分析一下动态代理的底层实现 类和接口 java动态代理的主要类和接口有:java.lang.reflect.Proxy.java.lang.reflect.InvocationHandler 1 java.lang.reflect.Proxy:动态代理机制的主类,提供一组静态方法为一组接口动态的生成对象和代理类. 1.public static InvocationHandler g

【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)

我们前面的三篇博文,简单易懂的介绍了为什么要使用IOC[实例讲解](二).和Spring的IOC原理[通俗解释](三)以及依赖注入的两种常用实现类型(四),这些都是刚开始学习Spring IoC容器时的基础内容,当然只有有了这些基础,我们才能走到今天更加详细的解析Spring的源码,深入理解IOC. 这篇我先简单的复习一下IoC,然后根据实例介绍IoC最基本的原理.废话少说,下面我们开始这篇博文的话题: 什么是IoC IoC容器,最主要的就是完成对象的创建以及维护对象的依赖关系等. 所谓控制反转

jdk的动态代理源码解析

先看一下JDK的动态是怎么用的. Java代码   package dynamic.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 实现自己的InvocationHandler * @author zyb * @since 2012-8-9 * */ public class MyInvocationH

做一个合格的程序猿之浅析Spring AOP源码(十八) Spring AOP开发大作战源码解析

其实上一篇文章价值很小,也有重复造轮子的嫌疑,网上AOP的实例很多,不胜枚举,其实我要说的并不是这个,我想要说的就是上一节中spring的配置文件: 我们这边并没有用到我们上几节分析的哪几个AOP的主要实现类:ProxyFactoryBean.java , ProxyFactory.java ,AspectJProxyFactory.java ,在我们这个配置文件中,根本没有显示的去配置这些类,那么spring到底是怎么做到的呢? 大家可以这么想,spring到底是怎么去杀害目标对象的呢?真正的

基于Struts2 Spring ibatis Oracle10g架构 多数据源动态切换实例

一.概述 基于Spring动态配置多数据源,在大型的应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效的提高系统的水平伸缩性,而这样的解决方案就会补同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求以及系统状态来动态的决定将数据存储在哪个数据库实例中,以及从哪个数据库提取数据. Spring配置多个数据源的方式和具体使用过程,Spring对于多数据源,以数据库表为参照,大体上可以分为两大类情况: 1.表级上的跨数据库,即对于不同的数据库却有不相同的表(表名和表结构完全

JDK1.8 动态代理机制及源码解析

动态代理 a) jdk 动态代理 Proxy, 核心思想:通过实现被代理类的所有接口,生成一个字节码文件后构造一个代理对象,通过持有反射构造被代理类的一个实例,再通过invoke反射调用被代理类实例的方法,来实现代理. 缺点:被代理类必须实现一个或多个接口 参考链接:http://rejoy.iteye.com/blog/1627405 源码解析:见第四部分 cglib 动态代理 核心思想:通过生成子类字节码实现,代理类为每个委托方法都生成两个方法,以add方法为例,一个是重写的add方法,一个

Spring核心框架 - AOP的原理及源码解析

一.AOP的体系结构 如下图所示:(引自AOP联盟) 层次3语言和开发环境:基础是指待增加对象或者目标对象:切面通常包括对于基础的增加应用:配置是指AOP体系中提供的配置环境或者编织配置,通过该配置AOP将基础和切面结合起来,从而完成切面对目标对象的编织实现. 层次2面向方面系统:配置模型,逻辑配置和AOP模型是为上策的语言和开发环境提供支持的,主要功能是将需要增强的目标对象.切面和配置使用AOP的API转换.抽象.封装成面向方面中的逻辑模型. 层次1底层编织实现模块:主要是将面向方面系统抽象封