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

网友投稿 224 2022-11-25

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 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。

org.springframework.boot

spring-boot-starter-data-jpa

org.projectlombok

lombok

1.18.22

provided

构建 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 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 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 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 即可,总之就是按照自己的需求扩展。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:你是怎么看待SaaS的?
下一篇:关于GD32E230系列MCU的性能分析和介绍
相关文章

 发表评论

暂时没有评论,来抢沙发吧~