加入收藏 | 设为首页 | 会员中心 | 我要投稿 站长网 (https://www.86zz.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

SpringBoot多数据源配置的全过程纪录

发布时间:2021-11-07 16:12:35 所属栏目:教程 来源:互联网
导读:目录 前言配置文件依赖构建 AbstractRoutingDataSource数据源切换目录总结 前言 多数据源的核心就是向 IOC 容器注入 AbstractRoutingDataSource 和如何切换数据源。注入的方式可以是注册 BeanDefinition 或者是构建好的 Bean,切换数据源的方式可以是方法参数
目录
前言配置文件依赖构建 AbstractRoutingDataSource数据源切换目录总结
前言
多数据源的核心就是向 IOC 容器注入 AbstractRoutingDataSource 和如何切换数据源。注入的方式可以是注册 BeanDefinition 或者是构建好的 Bean,切换数据源的方式可以是方法参数或者是注解切换(其他的没想象出来),具体由需求决定。
 
我的需求是统计多个库的数据,将结果写入另一个数据库,统计的数据库数量是不定的,无法通过 @Bean 直接注入,又是统计任务,DAO 层注解切换无法满足,因此选择注册(AbstractRoutingDataSource 的)BeanDefinition 和方法参数切换来实现。下面以统计统计中日韩用户到结果库为例。
 
配置文件
master 为结果库,其他为被统计的数据库(china、japan 可以用枚举唯一标识,当然也可以用 String):
 
dynamic:
  dataSources:
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/result?useUnicode=true&characterEncoding=utf8xxxxxxxx
      username: root
      password: 123456
    china:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/china?useUnicode=true&characterEncoding=utf8xxxxxxxx
      username: root
      password: 123456
    japan:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/japan?useUnicode=true&characterEncoding=utf8xxxxxxxx
      username: root
      password: 123456
    korea:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/korea?useUnicode=true&characterEncoding=utf8xxxxxxxx
      username: root
      password: 123456
对应的配置类:
 
package com.statistics.dynamicds.core.config;
 
import com.statistics.dynamicds.core.Country;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
 
import java.util.Map;
 
import static com.statistics.dynamicds.core.config.MultiDataSourceProperties.PREFIX;
 
@Data
@Configuration
@ConfigurationProperties(prefix = PREFIX)
public class MultiDataSourceProperties {
  public static final String PREFIX = "dynamic";
  private Map<Country, DataSourceProperties> dataSources;
 
  @Data
  public static class DataSourceProperties {
    private String driverClassName;
    private String url;
    private String username;
    private String password;
  }
}
package com.statistics.dynamicds.core;
 
public enum Country {
  MASTER("master", 0),
 
  CHINA("china", 86),
  JAPAN("japan", 81),
  KOREA("korea", 82),
  // 其他国家省略
 
  private final String name;
  private final int id;
 
  Country(String name, int id) {
    this.name = name;
    this.id = id;
  }
 
  public int getId() {
    return id;
  }
 
  public String getName() {
    return name;
  }
}
 
依赖
ORM 用的 JPA,SpringBoot 版本为 2.3.7.RELEASE,通过 Lombok 简化 GetSet。
 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
 
<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version>1.18.22</version>
     <scope>provided</scope>
</dependency>
 
构建 AbstractRoutingDataSource
Spring 的动态数据源需要注入 AbstractRoutingDataSource,因为配置文件中被统计数据源不是固定的,所以不能通过 @Bean 注解注入,需要手动构建。
 
要在启动类加上 @Import(MultiDataSourceImportBeanDefinitionRegistrar.class)。
 
要在启动类加上 @Import(MultiDataSourceImportBeanDefinitionRegistrar.class)。
 
要在启动类加上 @Import(MultiDataSourceImportBeanDefinitionRegistrar.class),重要的事情写三行。
 
package com.statistics.dynamicds.autoconfig;
 
import com.statistics.dynamicds.core.DynamicDataSourceRouter;
import com.statistics.dynamicds.core.Country;
import com.statistics.dynamicds.core.config.MultiDataSourceProperties;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
 
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.stream.Collectors;
 
import static com.statistics.dynamicds.core.config.MultiDataSourceProperties.PREFIX;
 
public class MultiDataSourceImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
  public static final String DATASOURCE_BEANNAME = "dynamicDataSourceRouter";
  private Environment environment;
 
  @Override
  public void registerBeanDefinitions(@Nonnull AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    MultiDataSourceProperties multiDataSourceProperties = Binder.get(environment)
            .bind(PREFIX, MultiDataSourceProperties.class)
            .orElseThrow(() -> new RuntimeException("no found dynamicds config"));
    final HikariDataSource[] defaultTargetDataSource = {null};
    Map<Country, HikariDataSource> targetDataSources = multiDataSourceProperties.getDataSources().entrySet().stream()
            .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    entry -> {
              MultiDataSourceProperties.DataSourceProperties dataSourceProperties = entry.getValue();
              HikariDataSource dataSource = DataSourceBuilder.create()
                      .type(HikariDataSource.class)
                      .driverClassName(dataSourceProperties.getDriverClassName())
                      .url(dataSourceProperties.getUrl())
                      .username(dataSourceProperties.getUsername())
                      .password(dataSourceProperties.getPassword())
                      .build();
              dataSource.setPoolName("HikariPool-" + entry.getKey());
              if (Country.MASTER == entry.getKey()) {
                defaultTargetDataSource[0] = dataSource;
              }
              return dataSource;
            }));
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DynamicDataSourceRouter.class)
            .addConstructorArgValue(defaultTargetDataSource[0])
            .addConstructorArgValue(targetDataSources)
            .getBeanDefinition();
    registry.registerBeanDefinition(DATASOURCE_BEANNAME, beanDefinition);
  }
 
  @Override
  public void setEnvironment(@Nonnull Environment environment) {
    this.environment = environment;
  }
}
 
上面代码中 MultiDataSourceProperties 不是由 @Resource 或者 @Autowired 获取的是因为 ImportBeanDefinitionRegistrar 执行的很早,此时 @ConfigurationProperties 的配置参数类还没有注入,因此要手动获取(加 @ConfigurationProperties 注解是为了使 IOC 容器中其他 Bean 能获取配置的 Country,以此来切换数据源)。
 
下面是 AbstractRoutingDataSource 的实现类 DynamicDataSourceRouter:
 
package com.statistics.dynamicds.core;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
import java.util.Map;
 
public class DynamicDataSourceRouter extends AbstractRoutingDataSource {
  public DynamicDataSourceRouter(Object defaultTargetDataSource, Map<Object, Object> targetDataSources) {
    this.setDefaultTargetDataSource(defaultTargetDataSource);
    this.setTargetDataSources(targetDataSources);
  }
 
  @Override
  protected Object determineCurrentLookupKey() {
    return DataSourceContextHolder.getLookupKey();
  }
}

(编辑:站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读