文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

SpringBoot多数据源配置的过程是什么

2023-06-25 13:11

关注

本篇内容主要讲解“SpringBoot多数据源配置的过程是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“SpringBoot多数据源配置的过程是什么”吧!

前言

多数据源的核心就是向 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();  }}

数据源切换

数据源的切换由 DataSourceContextHolder 和切面 DynamicDataSourceAspect 控制:

package com.statistics.dynamicds.core;public class DataSourceContextHolder {  private static final ThreadLocal<Country> HOLDER = ThreadLocal.withInitial(() -> Country.MASTER);  public static void setLookupKey(Country lookUpKey) {    HOLDER.set(lookUpKey);  }  public static Country getLookupKey() {    return HOLDER.get();  }  public static void clear() {    HOLDER.remove();  }}package com.statistics.dynamicds.core;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect@Componentpublic class DynamicDataSourceAspect {  @Pointcut("execution(* com.statistics.dao..*.*(..))")  void aspect() {  }  @Around("aspect()")  public Object around(ProceedingJoinPoint joinPoint) throws Throwable {    for (Object arg : joinPoint.getArgs()) {      if (arg instanceof Country) {        DataSourceContextHolder.setLookupKey((Country) arg);        break;      }    }    try {      return joinPoint.proceed();    }finally {      DataSourceContextHolder.clear();    }  }}

到此,相信大家对“SpringBoot多数据源配置的过程是什么”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯