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

目录
  • 前言
  • 配置文件
  • 依赖
  • 构建 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();
  }
}

数据源切换

数据源的切换由 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
@Component
public 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();
    }
  }
}

目录

.
└─com
    └─statistics
        │  StatisticsApplication.java
        │
        ├─dao
        │      UserDao.java
        │
        ├─dynamicds
        │  ├─autoconfig
        │  │      MultiDataSourceImportBeanDefinitionRegistrar.java
        │  │
        │  └─core
        │      │  DataSourceContextHolder.java
        │      │  DynamicDataSourceAspect.java
        │      │  DynamicDataSourceRouter.java
        │      │  Province.java
        │      │
        │      └─config
        │              MultiDataSourceProperties.java

总结

以上就完成了多数据源配置,使用时只需要按照在 dao 层的方法参数中加一个 Country 枚举就可以了。

如果无法用枚举标识数据源也可以换成 String,关于这个数据源的其他信息在内部类 DataSourceProperties 加一个 map 即可,总之就是按照自己的需求扩展。

(0)

相关推荐

  • springboot下配置多数据源的方法

    一.springboot 简介 SpringBoot使开发独立的,产品级别的基于Spring的应用变得非常简单,你只需"just run". 我们为Spring平台及第三方库提 供开箱即用的设置,这样你就可以有条不紊地开始.多数Spring Boot应用需要很少的Spring配置. 你可以使用SpringBoot创建Java应用,并使用 java -jar 启动它或采用传统的war部署方式.我们也提供了一个运行"spring 脚本"的命令行工具. 二.传统的Dat

  • Spring Boot多数据源及其事务管理配置方法

    准备工作 先给我们的项目添加Spring-JDBC依赖和需要访问数据库的驱动依赖. 配置文件 spring.datasource.prod.driverClassName=com.mysql.jdbc.Driver spring.datasource.prod.url=jdbc:mysql://127.0.0.1:3306/prod spring.datasource.prod.username=root spring.datasource.prod.password=123456 spring

  • Spring Boot 动态数据源示例(多数据源自动切换)

    本文实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便. 一配置二使用 1. 启动类注册动态数据源 2. 配置文件中配置多个数据源 3. 在需要的方法上使用注解指定数据源 1.在启动类添加 @Import({Dyna

  • springboot 多数据源的实现(最简单的整合方式)

    简介 相信大家有配置过多数据源,或者即将配置多数据的朋友们,会发现网上大概有以下几种方案: 1. 使用 AOP 切片进行动态数据源切换 2. 使用 MapperScan 的 basePackages 配置不同的 mapper 目录以及 template 3. 数据库代理中间件 这两种方式都能实现多数据源但是各有缺点: 1. 无法实现多数据源 XA 事物(全局事物管理 |JTA)这个缺点非常致命,配了多数据源但是没有全局事物那有什么用纯属坑爹,网上还有很多帖子教程使用这种虽然配置稍微简单但是如果你

  • SpringBoot项目中的多数据源支持的方法

    1.概述 项目中经常会遇到一个应用需要访问多个数据源的情况,本文介绍在SpringBoot项目中利用SpringDataJpa技术如何支持多个数据库的数据源. 具体的代码参照该 示例项目 2.建立实体类(Entity) 首先,我们创建两个简单的实体类,分别属于两个不同的数据源,用于演示多数据源数据的保存和查询. Test实体类: package com.example.demo.test.data; import javax.persistence.Entity; import javax.pe

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

    目录 前言 配置文件 依赖 构建 AbstractRoutingDataSource 数据源切换 目录 总结 前言 多数据源的核心就是向 IOC 容器注入 AbstractRoutingDataSource 和如何切换数据源.注入的方式可以是注册 BeanDefinition 或者是构建好的 Bean,切换数据源的方式可以是方法参数或者是注解切换(其他的没想象出来),具体由需求决定. 我的需求是统计多个库的数据,将结果写入另一个数据库,统计的数据库数量是不定的,无法通过 @Bean 直接注入,又

  • springboot多数据源配置及切换的示例代码详解

    注:本文的多数据源配置及切换的实现方法是,在框架中封装,具体项目中配置及使用,也适用于多模块项目 配置文件数据源读取 通过springboot的Envioment和Binder对象进行读取,无需手动声明DataSource的Bean yml数据源配置格式如下: spring: datasource: master: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver url:

  • SpringBoot多数据源配置详细教程(JdbcTemplate、mybatis)

    多数据源配置 首先是配置文件 这里采用yml配置文件,其他类型配置文件同理 我配置了两个数据源,一个名字叫ds1数据源,一个名字叫ds2数据源,如果你想配置更多的数据源,继续加就行了 spring: # 数据源配置 datasource: ds1: #数据源1 driver-class-name: com.mysql.jdbc.Driver # mysql的驱动你可以配置别的关系型数据库 url: jdbc:mysql://ip:3306/db1 #数据源地址 username: root #

  • SpringBoot多数据源配置并通过注解实现动态切换数据源

    目录 1. 环境准备 1.1 数据库准备 1.2 项目创建 2. ThreadLocal类介绍 3. AbstractRoutingDataSource类介绍 4. 具体实现 4.1 定义数据源枚举类 4.2 创建动态多数据源类 4.3 创建动态多数据源配置类 4.4 自定义注解用于指定数据源 4.5 AOP实现动态切换数据源 5. 测试使用 5.1 配置数据源 5.2 创建实体类 5.3 服务层代码 5.4 控制层代码 1. 环境准备 1.1 数据库准备 一个本地环境的MySQL数据库,数据库

  • springboot 多数据源配置不生效遇到的坑及解决

    目录 多数据源配置不生效遇到的坑 解决方案 踩坑SpringBoot配置多数据源,循环引用问题 解决办法 多数据源配置不生效遇到的坑 ** 同步数据时遇到多个数据源切换的问题,配置了yml文件时候发现启动的时候不加载数据源的配置. ** spring: datasource: db1: driver-class-name:xxxxxxx url:jdbc:xxxxxxx username:root password:111111 db2: driver-class-name:xxxxxx url

  • SpringBoot轻松整合MongoDB的全过程记录

    前言 MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的. 提示:以下是本篇文章正文内容,下面案例可供参考 一.技术介绍 1.MongoDB是什么? MongoDB(来自于英文单词"Humongous",中文含义为"庞大")是可以应用于各种规模的企业.各个行业以及各类应用程序的开源数据库.作为一个适用于敏捷开发的数据库,MongoDB的数据模式可以随着应用程序的发展而灵活地更新.与此同时,它也为开发人员 提供

  • SpringBoot创建RSocket服务器的全过程记录

    前言 在微服务的多样化世界中,HTTP是代理到代理通信中无可争议的领导者.它成熟,无处不在.但在某些情况下,HTTP请求-响应可能很麻烦.如果您需要传统请求-响应之外的通信模式,如fire-and-forget或streaming,该怎么办?如果你想向任何一个方向发送信息呢? 有了HTTP,有很多方法可以实现这一点,但这不是构建协议的目的.许多解决方案都带有额外的权衡或缺点.另外,这里没有规则手册说"你应该一直使用HTTP",像AMQP这样的消息传递协议已经证明了这一点.所以,知道你的

  • springboot打war包的全过程记录

    目录 为什么要把SpringBoot打成war包 springboot打war包 分步指南 总结 为什么要把SpringBoot打成war包 正常情况下SpringBoot项目是以jar包的形式,通过命令行: java -jar demo.jar 来运行的,并且SpringBoot是内嵌Tomcat服务器,所以每次重新启动都是用的新的Tomcat服务器.正因如此,也出现了一个问题:上传到项目的文件,如果是保存在项目中的,那么重启过后文件就会丢失.比如我们上传了一个头像,重启项目后,这个头像就没了

  • Nginx反向代理配置的全过程记录

    一.准备工作 Linux系统安装Tomcat,使用默认端口8080,启动Tomcat服务器 可以正常访问 接下来想要通过Nginx反向代理,转发请求到Tomcat服务器.对外暴露的是Nginx反向代理服务器的端口号,而Tomcat不对外暴露.浏览器不能直接访问到Tomcat,而是通过Nginx反向代理服务器才能访问到Tomcat 二.反向代理配置 在Windows系统的host文件进行域名和IP地址映射关系的配置 可以通过域名8080端口访问到Tomcat服务器 然后在Nginx进行请求转发的配

  • springboot v2.0.3版本多数据源配置方法

    本篇分享的是springboot多数据源配置,在从springboot v1.5版本升级到v2.0.3时,发现之前写的多数据源的方式不可用了,捕获错误信息如: 异常:jdbcUrl is required with driverClassName. 先来说下之前的多数据源配置如: spring: datasource: url: jdbc:sqlserver://192.168.122.111;DatabaseName=flight username: sa password: 1234.abc

随机推荐