当前位置: 首页> 汽车> 行情 > SpringBoot如何配置动态数据源?原理+实战

SpringBoot如何配置动态数据源?原理+实战

时间:2025/7/9 21:57:19来源:https://blog.csdn.net/weixin_42892177/article/details/141307914 浏览次数: 0次

若没空探究原理可直接跳转到“实现方式:注解+切面”目录

数据源切换方法

Spring对数据源的管理类似于策略模式,不懂策略模式也没关系,其实就是有一个全局的键值对,类型是Map<String, DataSource>。当JDBC操作数据库之时,会根据不同的key值选择不同的数据源。而这个key值可以放到方法的注解里。

所以切换数据源的思路就是让JDBC在获取数据源时根据key获取到要切换的数据源。

JDBC提供了AbstractRoutingDataSource抽象类,类名意思是数据源路由,该类提供了一个抽象方法determineCurrentLookupKey(),切换数据源时JDBC会调用这个方法获取数据源的key,所以只需要实现该方法,改变该方法中返回的key值即可。

源码解读

1.从类关系图中可以看出AbstractRoutingDataSource类实现了DataSource接口,后者有两个getConnection()方法,即获取DB连接的作用。

在这里插入图片描述

2.AbstractRoutingDataSource实现了这两个方法

在这里插入图片描述

其中determineTargetDataSource()方法的作用就是获取实际的数据源,其内部调用了determineCurrentLookupKey()方法,取到当前设定的key,通过key在上下文this.resolvedDataSources属性中尝试获取DataSource对象,这个对象即当前连接的数据源

在这里插入图片描述

3.那么this.resolvedDataSources在哪里维护呢? 继续在AbstractRoutingDataSource类里找,可以找到afterPropertiesSet()方法,这个方法是InitializingBean接口的,作用是在bean的所有属性设置完成后便会调用此方法。可以看到this.resolvedDataSources是从this.targetDataSources取的信息。

在这里插入图片描述

所以只需要改变this.targetDataSources,即可改变this.resolvedDataSources;后续改变determineCurrentLookupKey()的返回值(key),在调用getConnection()时即可获取到指定的数据源

实现方式:注解+切面

别看步骤挺多,但其实很容易理解和使用

1.配置文件示例:

spring:datasource: master: # 数据源masterjdbc-url: jdbc:mysql://localhost:3306/master?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverdb1: # 数据源1jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver

2.创建数据源配置类

创建数据源配置类(我这里为了方便区分就为每一个数据源创建了一个配置类,当然也可以把所有的数据源配置在一个类里)

@Configuration
@EnableConfigurationProperties({MasterDataSourceProperties.class})
public class MasterDataSourceConfig {/*** 这个MasterDataSourceProperties是读取配置文件的类,我这里为了省篇幅就不展示了**/@Autowiredprivate MasterDataSourceProperties masterDataSourceProperties;@Bean@Primarypublic DataSource masterDataSource() {DruidDataSource datasource = new DruidDataSource();datasource.setUrl(masterDataSourceProperties.getUrl());datasource.setUsername(masterDataSourceProperties.getUsername());datasource.setPassword(AESUtil.aesDecode(masterDataSourceProperties.getPassword()));datasource.setDriverClassName(masterDataSourceProperties.getDriverClassName());......return datasource;}
}
@Configuration
@EnableConfigurationProperties({OdsDataSourceProperties.class})
public class DB1DataSourceConfig {/*** 这个DB1DataSourceProperties是读取配置文件的类,我这里为了省篇幅就不展示了**/@Autowiredprivate DB1DataSourceProperties dB1DataSourceProperties;@Beanpublic DataSource db1DataSource() {DruidDataSource datasource = new DruidDataSource();datasource.setUrl(dB1DataSourceProperties.getUrl());datasource.setUsername(dB1DataSourceProperties.getUsername());datasource.setPassword(AESUtil.aesDecode(dB1DataSourceProperties.getPassword()));datasource.setDriverClassName(dB1DataSourceProperties.getDriverClassName());......return datasource;}
}

3.创建DynamicDataSource

创建自己的一个DynamicDataSource类(名字任意)继承AbstractRoutingDataSource,维护数据源,提供切换方法。

public class DynamicDataSource extends AbstractRoutingDataSource {/*** 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可*/@Overrideprotected DataSource determineTargetDataSource() {return super.determineTargetDataSource();}/*** 如果希望所有数据源在启动配置时就加载好,然后通过设置数据源Key值来切换数据,定制这个方法*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceKey();}/*** 设置默认数据源** @param defaultDataSource*/public void setDefaultDataSource(Object defaultDataSource) {super.setDefaultTargetDataSource(defaultDataSource);}/*** 设置数据源** @param dataSources*/public void setDataSources(Map<Object, Object> dataSources) {super.setTargetDataSources(dataSources);// 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());}
}

4.创建数据源上下文处理器DynamicDataSourceContextHolder

创建数据源上下文处理器DynamicDataSourceContextHolder用以存储当前线程需要使用的数据源名称。

public class DynamicDataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {/*** 将 master 数据源的 key作为默认数据源的 key*/@Overrideprotected String initialValue() {return "master";}};/*** 数据源的 key集合,用于切换时判断数据源是否存在*/public static List<Object> dataSourceKeys = new ArrayList<>();/*** 切换数据源** @param key*/public static void setDataSourceKey(String key) {contextHolder.set(key);}/*** 获取数据源** @return*/public static String getDataSourceKey() {return contextHolder.get();}/*** 重置数据源*/public static void clearDataSourceKey() {contextHolder.remove();}/*** 判断是否包含数据源** @param key 数据源key* @return*/public static boolean containDataSourceKey(String key) {return dataSourceKeys.contains(key);}/*** 添加数据源keys** @param keys* @return*/public static boolean addDataSourceKeys(Collection<? extends Object> keys) {return dataSourceKeys.addAll(keys);}
}

5.创建数据源配置类DataSourceConfig

创建数据源配置类DataSourceConfig,将所有数据源注入到spring容器

@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class }) // 排除 DataSourceAutoConfiguration 的自动配置,避免环形调用
public class DataSourceConfig {@Autowiredprivate MasterDataSourceConfig masterDataSourceConfig;@Autowiredprivate DB1DataSourceConfig dB1DataSourceConfig;/*** 设置动态数据源为主数据源** @return*/@Bean@Primarypublic DynamicDataSource dataSource() {DynamicDataSource dynamicDataSource = new DynamicDataSource();// 默认指定的数据源dynamicDataSource.setDefaultDataSource(masterDataSourceConfig.masterDataSource());// 将数据源设置进mapMap<Object, Object> dataSourceMap = new HashMap<>(8);dataSourceMap.put(DataSourceEnum.MASTER.toString(), masterDataSourceConfig.masterDataSource());dataSourceMap.put(DataSourceEnum.DB1.toString(), dB1DataSourceConfig.db1DataSource());// 使用 Map 保存多个数据源,并设置到动态数据源对象中,这个值最终会在afterPropertiesSet中被设置到resolvedDataSources上dynamicDataSource.setDataSources(dataSourceMap);return dynamicDataSource;}
}

6.创建数据源类型枚举DataSourceEnum

public enum DataSourceEnum {/**默认类型*/MASTER,/**DB1类型*/DB1,;
}

7.创建自定义注解@DataSource

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {/*** 数据源key值* @return*/DataSourceEnum value();
}

8.创建切面DynamicDataSourceAspect

@Slf4j
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {/*** 切换数据源** @param point* @param dataSource*/@Before("@annotation(dataSource))")public void switchDataSource(JoinPoint point, DataSource dataSource) {if (!DynamicDataSourceContextHolder.containDataSourceKey(dataSource.value().toString())) {log.info("DataSource [{}] doesn't exist, use default DataSource", dataSource.value());} else {// 切换数据源DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value().toString());log.info("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(),point.getSignature());}}/*** 重置数据源** @param point* @param dataSource*/@After("@annotation(dataSource))")public void restoreDataSource(JoinPoint point, DataSource dataSource) {// 将数据源置为默认数据源DynamicDataSourceContextHolder.clearDataSourceKey();log.info("Restore DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());}
}

如何使用

@Override
@DataSource(DataSourceEnum.DB1)
public Page<AuditTaskDto> queryAuditTask(AuditTaskQuery query) {Page<AuditTaskDto> page = baseMapper.queryAuditTask(query);return page;
}

如此就可以直接使用自定义的@DataSource注解来切换数据源啦~~~

关键字:SpringBoot如何配置动态数据源?原理+实战

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: