数据源动态切换

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) {
       ...
    }

    
}

### 回答1: Spring Boot是一个用于简化Spring应用开发的框架,可以方便地实现多数据源动态切换功能。 在Spring Boot中,通过配置多个数据源bean,可以实现多数据源的使用。首先,在application.properties或application.yml文件中配置多个数据源的相关属性,例如数据库连接信息、驱动程序等。然后,在Spring Boot的配置类中,通过@Bean注解将数据源bean配置到Spring容器中。 为了实现动态切换数据源,可以使用ThreadLocal或者AOP进行数据源切换。首先,可以定义一个DataSourceContextHolder类,使用ThreadLocal来存储当前线程使用的数据源标识。然后,可以在需要切换数据源的地方,通过调用DataSourceContextHolder的setDataSourceKey方法,设置当前线程使用的数据源标识。在数据访问层的代码中,可以通过调用DataSourceContextHolder的getDataSourceKey方法获取当前线程使用的数据源标识,然后根据该标识来获取对应的数据源bean。 除了使用ThreadLocal,还可以使用AOP来实现数据源切换。可以定义一个切面,通过在切面中获取注解或者方法名来确定使用的数据源,然后通过切换数据源的方式来实现动态切换。 通过以上的方式,就可以实现Spring Boot的多数据源动态切换功能。不同的数据源可以根据自己的需求和业务场景进行配置和使用,提高系统的扩展性和灵活性。 ### 回答2: Spring Boot提供了方便的配置和集成多数据源,并且支持动态切换数据源。下面将通过一个简单的示例来说明如何在Spring Boot中实现多数据源动态切换。 首先,在pom.xml文件中添加Spring Boot数据源相关的依赖项,比如Spring Boot Starter、MyBatis、Druid等。然后,在application.properties文件中配置数据源的相关信息,包括数据库的URL、用户名、密码等。 接下来,创建多个数据源的配置类,通过@Configuration注解将其标记为配置类,并使用@Bean注解来创建数据源对象。在创建数据源对象时,可以使用DruidDataSource或者其他适配的数据源类。 在创建数据源配置类时,可以使用@Primary注解来指定一个主数据源,该主数据源将作为默认数据源使用。对于其他数据源,可以使用@Qualifier注解进行标识。 然后,在创建MyBatis的SqlSessionFactoryBean时,使用@MapperScan注解来扫描并加载Mapper接口。在其中配置数据源,可以通过注入的方式获取数据源对象,并将其设置到SqlSessionFactoryBean中。 最后,在需要切换数据源的地方,可以通过使用AOP切面和动态切换数据源的方式来实现。可以创建一个DataSourceAspect切面类,在其中定义切点和通知,通过在方法上添加@DataSource注解来指定要切换数据源。在通知方法中,通过读取注解上的参数来确定要切换数据源,并将其设置到ThreadLocal变量中。 总结起来,Spring Boot数据源动态切换的步骤包括添加依赖项、配置数据源、创建数据源配置类、配置MyBatis的SqlSessionFactoryBean以及使用AOP实现动态切换数据源。通过这些步骤,我们可以在Spring Boot中轻松实现多数据源动态切换。 ### 回答3: 在Spring Boot中实现多数据源动态切换可以通过以下步骤实现: 1. 配置数据源:在application.properties或yml文件中配置多个数据源的连接信息,每个数据源都有独立的数据源配置属性。 2. 创建数据源工厂:使用Spring的Bean注解创建多个数据源对象,并分别设置其相关属性。 3. 创建数据源路由器:创建一个数据源路由器类,该类包含一个ThreadLocal变量,用于保存当前线程所使用的数据源标识。 4. 创建数据源切换注解:使用自定义注解方式,通过在Service层的方法上加上注解,来选择对应的数据源。 5. 创建切面:使用AOP的方式,在方法执行前获取选择的数据源标识,并存入数据源路由器的ThreadLocal变量中。 6. 创建数据源切换切入点:在切面中设置一个切入点,用于匹配加上数据源切换注解的方法。 7. 配置数据源切面:使用Spring的Bean注解配置切面类。 8. 启动类中配置数据源切换:在启动类中添加@EnableAspectJAutoProxy注解来开启AOP,同时使用@Import注解引入切面类。 9. 使用数据源切换注解:在Service层的方法上加上数据源切换注解,指定使用哪个数据源。 通过以上步骤,就可以在使用Spring Boot时实现多数据源动态切换。在需要切换数据源的地方,只需要使用自定义的注解来指定数据源切换的过程由切面和数据源路由器来完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值