数据源动态切换

本文详细介绍了如何在Spring Boot中创建并管理多个数据源,包括使用@ConfigurationProperties创建数据源,实现AbstractRoutingDataSource进行数据源路由,通过ThreadLocal实现动态数据源切换,以及使用com.baomidou:dynamic-datasoure-spring-boot-starter库进行动态数据源的自动配置和切换。此外,还展示了动态数据源的配置文件示例和注解驱动的数据源切换方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、创建多个数据源

@ConfigurationProperties+
org.springframework.boot.jdbc.DataSourceBuilder.create().build()

2、实现该接口org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

// 父类接口DataSource方法Connection getConnection(),Connection getConnection(String 
//                        username, String password)
public abstract class AbstractRoutingDataSource {
    
    // DataSourceLookup接口的方法DataSource getDataSource(String var1)
    //  DataSource dataSource = jndiTemplate.lookup(var1, DataSource.class)
    // 作用根据jndiTemplate,获取DataSource对象
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

    // 储存多个数据源,包括默认数据源
    private Map<Object, Object> targetDataSources;
    
    // 储存多个数据源,包括默认数据源(解析后)
    // key=lookupKey,value=dataSource
    private Map<Object, DataSource> resolvedDataSources;
    
    // 默认数据源
    private Object defaultTargetDataSource;
    
    // 默认数据源(解析后)
    private DataSource resolvedDefaultDataSource;
    
    // 设置多个数据源
    public void setTargetDataSources(Map<Object, Object> targetDataSources);
    
    // 设置默认数据源
    public void setDefaultTargetDataSource(Object defaultTargetDataSource);

    //  Connection getConnection()通过determineTargetDataSource().getConnection()获取连接
    //  DataSource determineTargetDataSource()方法是最重要的方法
    DataSource determineTargetDataSource() {
        
        // 实现子类,重写determineCurrentLookupKey()方法
        // 取得lookupKey
        Object lookupKey = this.determineCurrentLookupKey();
        
        // 在resolvedDataSources的map中根据lookupKey获取数据源
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        
        // 如果dataSoure为null,则将resolvedDefaultDataSource赋值给dataSource
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        
        // 如果dataSoure还为null,则抛出异常
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for         
                      lookup key [" + lookupKey + "]");
        } else {
            //如果dataSoure!=null,则返回 
            return dataSource;
        }
    }
}

3、实现Object lookupKey = determineCurrentLookupKey()方法

@Override
protected Object determineCurrentLookupKey() {
    // 动态切换数据源
	return DataSourceDynamicKey.getCurrentLookupKey();
}

public class DataSourceDynamicKey {
   // currentLookupKey放在ThreadLocal中
   private static final ThreadLocal<String> localLookupKey = new ThreadLocal<String>();
    
   // 设置localLookupKey的值
   public static void dynamicKey(String currentLookupKey);
   
   // 返回localLookupKey的值
   public static void getCurrentLookupKey();
    
   // 移除ThreadLocal的值
   public static void remove();
}

4、自动动态切换数据

         SourceDynamicKey.dynamicKey(..)实现了类调用静态方法的动态切换数据源。也可以通过注解+aop(@before)实现自动动态切换数据源。

5、学习com.baomidou:dynamic-datasoure-spring-boot-stater:2.5.6的动态数据源

        5.1
# 多主多从                      纯粹多库(记得设置primary)                   混合配置
spring:                               spring:                               spring:
  datasource:                           datasource:                           datasource:
    dynamic:                              dynamic:                              dynamic:
      datasource:                           datasource:                           datasource:
        master_1:                             mysql:                                master:
        master_2:                             oracle:                               slave_1:
        slave_1:                              sqlserver:                            slave_2:
        slave_2:                              postgresql:                           oracle_1:
        slave_3:                              h2:                                   oracle_2:
5.2 使用@DS("dsName")  切换数据源
5.3 源码分析
5.3.1 自动配置(在spring.factories中配置DynamicDataSourceAutoConfiguration类)
5.3.2 DynamicDataSourceAutoConfiguration类


public class DynamicDataSourceAutoConfiguration {
    
    // 读取数据源属性
    // 
    @Autowired
    private DynamicDataSourceProperties properties;

    
    // 没有DsProcessor实现对象时,创建该对象
    // 1.从请求header中获取数据源key。2.从请求session中获取数据源key
    // 3.从方法参数中获取数据源key。
    @Bean
    @ConditionalOnMissingBean
    public DsProcessor dsProcessor() {
       
        DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
        DsSessionProcessor sessionProcessor = new DsSessionProcessor();
        DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
        headerProcessor.setNextProcessor(sessionProcessor);
        sessionProcessor.setNextProcessor(spelExpressionProcessor);
        // 返回headerProcessor。DsHeaderProcessor->DsSessionProcessor- 
        //    >DsSpelExpressionProcessor
        return headerProcessor;
    }


    @Bean
    @ConditionalOnMissingBean
    public DynamicDataSourceAnnotationAdvisor 
                     dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
        // dsProcessor上面返回的对象
        // interceptor对象保存dsProcessor对象
        DynamicDataSourceAnnotationInterceptor interceptor = new 
                            DynamicDataSourceAnnotationInterceptor();
        interceptor.setDsProcessor(dsProcessor);
        // advisor对象保存interceptor对象和拦截@DS注解
        DynamicDataSourceAnnotationAdvisor advisor = new 
                     DynamicDataSourceAnnotationAdvisor(interceptor);
        // 设置循序
        advisor.setOrder(this.properties.getOrder());
        return advisor;
    }

    @Bean
    @ConditionalOnMissingBean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        // 接口DynamicDataSourceProvider
        //    Map<String, DataSource> loadDataSources()加载数据源
        return new YmlDynamicDataSourceProvider(this.properties);
    }

    @Bean
    @ConditionalOnMissingBean
    // 创建DynamicDataSourceCreator,设置全局的druid和hikari和publicKey
    //   静态代码块 初始化druidExists,hikariExists
    public DynamicDataSourceCreator dynamicDataSourceCreator() {
        DynamicDataSourceCreator dynamicDataSourceCreator = new 
                      DynamicDataSourceCreator();
        dynamicDataSourceCreator.setDruidGlobalConfig(this.properties.getDruid());
        dynamicDataSourceCreator.setHikariGlobalConfig(this.properties.getHikari());
        dynamicDataSourceCreator.setGlobalPublicKey(this.properties.getPublicKey());
        return dynamicDataSourceCreator;
    }
    
    @Bean
    @ConditionalOnMissingBean
    // 获取数据源
    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(this.properties.getPrimary());
        dataSource.setStrategy(this.properties.getStrategy());
        // 数据源提供
        dataSource.setProvider(dynamicDataSourceProvider);
        dataSource.setP6spy(this.properties.getP6spy());
        dataSource.setStrict(this.properties.getStrict());
        // springIOC创建dataSource,调用dataSource.afterPropertiesSet()
        return dataSource;
    }
}


@ConfigurationProperties(
    prefix = "spring.datasource.dynamic"
)
public class DynamicDataSourceProperties {
    // 默认master
    private String primary = "master";
    
    // 储存多个数据源
    // 1.第一次初始化DataSourceProperty中publicKey为null
    // 2.DataSourceProperty对象获取(get方法)username和password和url中,有decrypt(...)方法
    private Map<String, DataSourceProperty> datasource = new LinkedHashMap();

    // 读取spring.datasource.dynamic.druid中属性,配置druid数据源
    @NestedConfigurationProperty
    private DruidConfig druid = new DruidConfig();
    // 读取spring.datasource.dynamic.hikari中属性,配置hikari数据源
    @NestedConfigurationProperty
    private HikariCpConfig hikari = new HikariCpConfig();
    
    // 使用数据源的策略。默认平衡(原理AtomicInteger)
    //    RandomDynamicDataSourceStrategy 
    private Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;
    
    // 默认公钥。使用公钥解码
    private String publicKey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ4o6sn4WoPmbs7DR9mGQzuuUQM9erQTVPpwxIzB0ETYkyKffO097qXVRLA6KPmaV+/siWewR7vpfYYjWajw5KkCAwEAAQ==";

}

// 1. DsSessionProcessor @DS中的value的值为#session.datasource。则获取请求session中属性 
//          datasource的值
// 2. DsSpelExpressionProcessor 带有@DS("#xx")的方法method(String xx),则获取参数xx的值
public class DsHeaderProcessor {
    // 抽象类DsProcessor中的属性
    private DsProcessor nextProcessor; 

    private static final String HEADER_PREFIX = "#header";
    
    // 重写抽象类的方法
    // key为@DS中的value
    public boolean matches(String key) {
        return key.startsWith("#header");
    }
    
    // 重写抽象类的方法
    // 从请求头datasource中获取数据源字符串。例如 key="#header.datasource"
    public String doDetermineDatasource(MethodInvocation invocation, String key) {
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getHeader(key.substring(8));
    }

    // 抽象类DsProcessor中的方法
    public String determineDatasource(MethodInvocation invocation, String key) {
        // key是否以#header开头
        if (this.matches(key)) {
            String datasource = this.doDetermineDatasource(invocation, key);
            // 请求头中获取为null时且nextProcessor不为null,则执行nextProcessor返回
            // 否则返回
            return datasource == null && this.nextProcessor != null ? this.nextProcessor.determineDatasource(invocation, key) : datasource;
        } else {
            // 不以#header开头,nextProcessor不为null时,则执行nextProcessor返回
            // 否则返回null;
            return this.nextProcessor != null ? this.nextProcessor.determineDatasource(invocation, key) : null;
        }
    }
}

// 动态数据源切面(增强和切入点)
public class DynamicDataSourceAnnotationAdvisor {
    // 增强逻辑
    // advice为dynamicDataSourceAnnotationInterceptor对象
    private Advice advice;
    // 切入点
    // this.pointcu = this.buildPointcut();
    private Pointcut pointcut;
    
    // 建立@DS的注解和方法接入点的拦截
    private Pointcut buildPointcut() {
        Pointcut cpc = new AnnotationMatchingPointcut(DS.class, true);
        Pointcut mpc = AnnotationMatchingPointcut.forMethodAnnotation(DS.class);
        return (new ComposablePointcut(cpc)).union(mpc);
    }
}

// 该类为MethodInterceptor接口实现类。方法Object invoke(@Nonnull MethodInvocation var1) 
//       throws Throwable;
public class DynamicDataSourceAnnotationInterceptor {
    
    private static final String DYNAMIC_PREFIX = "#";
    // 解析@DS注解
    private static final DynamicDataSourceClassResolver RESOLVER = new DynamicDataSourceClassResolver();
    // 处理逻辑
    private DsProcessor dsProcessor;

    
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object var2;
        try {
            // 执行方法前
            // DynamicDataSourceContextHolder类.clear()则调用remove()
            //        ThreadLocal<Deque<String>>  接口Deque为ArrayDeque类
            // push(...)左推数据源key。key为null时,推""
            DynamicDataSourceContextHolder.push(this.determineDatasource(invocation));
            var2 = invocation.proceed();
        } finally {
            // 方法执行后
            // poll(...)右弹数据源key。右弹完后,执行remove()
            DynamicDataSourceContextHolder.poll();
        }

        return var2;
    }
    
    // 先获取方法的@DS注解,没有则获取类@DS注解
    // 获取@DS注解的value 
    private String determineDatasource(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        DS ds = method.isAnnotationPresent(DS.class) ? (DS)method.getAnnotation(DS.class) : (DS)AnnotationUtils.findAnnotation(RESOLVER.targetClass(invocation), DS.class);
        String key = ds.value();
        // key以#开头,执行dsProcessor。否则返回key
        return !key.isEmpty() && key.startsWith("#") ? this.dsProcessor.determineDatasource(invocation, key) : key;

        
}

// 
public class DynamicRoutingDataSource {
    private static final String UNDERLINE = "_";
    // 多数据源
    private DynamicDataSourceProvider provider;

    // 默认平衡调用数据源
    private Class<? extends DynamicDataSourceStrategy> strategy;
    
    // 储存多个数据源对象
    private Map<String, DataSource> dataSourceMap = new LinkedHashMap();

     private Map<String, DynamicGroupDataSource> groupDataSources = new ConcurrentHashMap();
    
    // 
    public void afterPropertiesSet() throws Exception {
        // YmlDynamicDataSourceProvider.loadDataSources()
        // 
        Map<String, DataSource> dataSources = this.provider.loadDataSources()->{
            Map<String, DataSourceProperty> dataSourcePropertiesMap = 
                                    this.properties.getDatasource();
            return this.createDataSourceMap(dataSourcePropertiesMap)->{
                            dataSourceMap.put(pollName, 
                // 使用全局配置,创建DataSource对象(解码属性)
                this.dynamicDataSourceCreator.createDataSource(dataSourceProperty));
            };
        };
        Iterator var2 = dataSources.entrySet().iterator();

        while(var2.hasNext()) {
            Map.Entry<String, DataSource> dsItem = (Map.Entry)var2.next();
            // 向dataSourceMap添加数据源
            // 如果数据源key包含‘_’,加入groupDataSources以key.split("_")[0]加入
            this.addDataSource((String)dsItem.getKey(), (DataSource)dsItem.getValue());
        }

        if (this.groupDataSources.containsKey(this.primary)) {
            log.info("dynamic-datasource initial loaded [{}] datasource,primary group datasource named [{}]", dataSources.size(), this.primary);
        } else {
            if (!this.dataSourceMap.containsKey(this.primary)) {
                throw new RuntimeException("dynamic-datasource Please check the setting of primary");
            }

            log.info("dynamic-datasource initial loaded [{}] datasource,primary datasource named [{}]", dataSources.size(), this.primary);
        }

    }
    
    // 得到数据源连接
    public Connection getConnection() throws SQLException {
        return this.determineDataSource().getConnection();
    }
    
    // 根据threadLocal获取数据源
    public DataSource determineDataSource() {
        return this.getDataSource(DynamicDataSourceContextHolder.peek());
    }
        
    // 1.根据ds从map中取
    // 2.ds为null时或其他情况,取主数据源(组能取到先取组)
    public DataSource getDataSource(String ds) {
       ...
    }

    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值