本篇内容主要讲解“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多数据源配置的过程是什么”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!