Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)

在 Spring Boot 生态中,dynamic-datasource-spring-boot-starter 是一个非常实用的组件,它为我们在多数据源场景下 提供了便捷的解决方案。在上一篇文章《一分钟上手:如何创建你的第一个 Spring Boot Starter》中,我们学习了如何创建自己的 starter,今天我们就来深入探究下 dynamic-datasource-spring-boot-starter 的源码,了解它是如何实现动态数据源切换功能的。

一、dynamic-datasource-spring-boot-starter介绍

dynamic-datasource-spring-boot-starter 是一个用于在 Spring Boot 项目中实现动态数据源切换的工具

在实际的应用开发中,经常会遇到需要连接多个数据源的情况,例如一个销售系统会根据不同的业务模块,如线索、订单、库存、物流等连接到不同的数据库。手动管理多个数据源的切换和配置是一项复杂且容易出错的任务,而这个 starter 就是为了解决这些问题而生。

二、依赖的jar包

1. SpringBoot相关依赖

提供了JDBC数据库连接操作、AOP(面向切面编程)、Actuator应用监控、Web应用开发等功能,Actuator和Web依赖都是可选的,不是必须的

复制代码
	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <optional>true</optional>
        </dependency>

2. MyBatis-Plus

增强版的MyBatis框架,可以极大简化数据库操作,业务的mapper层只需要继承BaseMapper,即可使用mybatis-plus提供的增删改查的API 。使开发者能够更加专注于业务逻辑的实现,而无需在复杂的 SQL 编写和数据库连接管理上耗费过多精力。该jar包也是可选的,不是必须的

3. 连接池

HikariCP、Druid、Beecp、Commons DBCP2等用于管理数据库连接。

这些jar包也都是可选的 ,后端开发使用的最多的还是Druid数据库连接池

复制代码
	<dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP-java7</artifactId>
            <version>${hikariCp.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.github.chris2018998</groupId>
            <artifactId>beecp</artifactId>
            <version>${beeCp.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>${dbcp2.version}</version>
            <optional>true</optional>
        </dependency>

4. 配置处理器

在开发阶段为 IDE 提供辅助功能,如代码提示和验证配置属性的正确性 。在项目的实际运行时,并不需要这个依赖来执行应用程序的核心逻辑,所以也将其设置为可选,避免了不必要的依赖传递,减小了项目最终打包的体积

复制代码
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

5. P6Spy

用于监控和调试SQL语句。可能在开发和测试环境中非常有用,但在生产环境中可能不需要,所以业将其标记为可选依赖,让使用方根据实际情况决定是否引入。

6. Seata

提供分布式事务解决方案,根据项目实际需要决定要不要引入,所以该jar包也是可选的。

三、DynamicDataSourceAutoConfiguration类剖析

DynamicDataSourceAutoConfiguration类是dynamic-datasource-spring-boot-starter包的自动配置类,整个动态数据源的配置和初始化都在这里。

复制代码
@Configuration
@EnableConfigurationProperties({DynamicDataSourceProperties.class})
@AutoConfigureBefore(
    value = {DataSourceAutoConfiguration.class},
    name = {"com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure"}
)
@Import({DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class, DynamicDataSourceHealthCheckConfiguration.class})
@ConditionalOnProperty(
    prefix = "spring.datasource.dynamic",
    name = {"enabled"},
    havingValue = "true",
    matchIfMissing = true
)
public class DynamicDataSourceAutoConfiguration {
	......
}

这种配置类相关的注解之前的博客也分享过,这里就不再赘述了,我们重点来剖析下该配置类中的几个关键方法。

1. dynamicDataSourceProvider方法

用于创建动态数据源提供者 DynamicDataSourceProvider。其主要职责是根据配置文件中的数据源信息构建数据源对象,并将其提供给动态数据源路由机制。

复制代码
	@Bean
    @ConditionalOnMissingBean 
    // 确保Spring容器中不存在 DynamicDataSourceProvider 类型的 Bean 时才创建该 Bean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        Map<String, DataSourceProperty> datasourceMap = this.properties.getDatasource();
        return new YmlDynamicDataSourceProvider(datasourceMap);
    }

2. dataSource方法

用于是创建动态数据源 DynamicRoutingDataSource。它整合了各种配置信息,包括默认数据源、严格模式、切换策略、数据源提供者以及与SQL语句监控和分布式事务相关的配置,构建出一个完整的动态数据源对象,使其能够在运行时根据不同条件动态切换数据源。

复制代码
	@Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        // 默认数据源
        dataSource.setPrimary(this.properties.getPrimary());
        // 根据配置决定在数据源切换时是否进行严格检查,以确保数据源的正确性和一致性。
        dataSource.setStrict(this.properties.getStrict());
        // 数据源切换策略,如基于注解、基于配置文件或基于特定规则的切换策略
        dataSource.setStrategy(this.properties.getStrategy());
        dataSource.setProvider(dynamicDataSourceProvider);
        dataSource.setP6spy(this.properties.getP6spy());
        dataSource.setSeata(this.properties.getSeata());
        return dataSource;
    }

3. dynamicDatasourceAnnotationAdvisor 方法

负责创建动态数据源注解切面的Advisor。用于在方法执行时根据注解信息进行数据源切换。

复制代码
	@Role(2)
    @Bean
    public Advisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
        DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(this.properties.isAllowedPublicOnly(), dsProcessor);
        DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
        advisor.setOrder(this.properties.getOrder());
        return advisor;
    }

4. dynamicTransactionAdvisor 方法

用于创建动态事务 Advisor。它定义了一个基于注解的事务切面,用于在方法执行时管理事务,确保数据库操作的原子性、一致性、隔离性和持久性。

复制代码
	@Role(2)
    @ConditionalOnProperty(
        prefix = "spring.datasource.dynamic",
        name = {"seata"},
        havingValue = "false",
        matchIfMissing = true
    )
    @Bean
    public Advisor dynamicTransactionAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("@annotation(com.baomidou.dynamic.datasource.annotation.DSTransactional)");
        return new DefaultPointcutAdvisor(pointcut, new DynamicLocalTransactionAdvisor());
    }

5. dsProcessor 方法

用于创建数据源处理器 DsProcessor。数据源处理器负责根据不同的条件(如请求头、会话信息、SpEL 表达式等)确定要切换的数据源,是实现动态数据源切换逻辑的关键组件之一。

自动配置类剖析完了,剩下的就是配置属性类DynamicDataSourceProperties和spring.factories 文件了。

复制代码
@ConfigurationProperties(
    prefix = "spring.datasource.dynamic"
)
public class DynamicDataSourceProperties {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceProperties.class);
    public static final String PREFIX = "spring.datasource.dynamic";
    public static final String HEALTH = "spring.datasource.dynamic.health";
    public static final String DEFAULT_VALID_QUERY = "SELECT 1";
    private String primary = "master";
    private Boolean strict = false;
    private Boolean p6spy = false;
    private Boolean seata = false;
    private Boolean lazy = false;
    private SeataMode seataMode;
    private boolean health;
    private String healthValidQuery;
    private Map<String, DataSourceProperty> datasource;
    private Class<? extends DynamicDataSourceStrategy> strategy;
    private Integer order;
    @NestedConfigurationProperty
    private DruidConfig druid;
    @NestedConfigurationProperty
    private HikariCpConfig hikari;
    @NestedConfigurationProperty
    private BeeCpConfig beecp;
    @NestedConfigurationProperty
    private Dbcp2Config dbcp2;
    private String publicKey;
    private boolean allowedPublicOnly;
    ......
}

DynamicDataSourceProperties一般都配置在Apollo中。

以下是spring.factories文件。

复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=

com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration

四、总结

通过对 dynamic-datasource-spring-boot-starter 的剖析,我们可以了解到它是实现动态数据源切换的机制。这一组件为开发者在处理多数据源场景时提供了极大的便利,使得应用的可扩展性和可维护性得到了显著提升。

相关推荐
zcl_199117 小时前
记一次ThreadLocal导致的生产事故
java
ruleslol17 小时前
SpringBoot13-文件上传02-阿里云OSS
spring boot
RoboWizard17 小时前
怎么判断我的电脑是否支持PCIe 5.0 SSD?Kingston FURY Renegade G5
java·spring·智能手机·电脑·金士顿
武子康17 小时前
大数据-139 ClickHouse MergeTree 最佳实践:Replacing 去重、Summing 求和、分区设计与物化视图替代方案
大数据·后端·nosql
该用户已不存在17 小时前
7个让全栈开发效率起飞的 Bun 工作流
前端·javascript·后端
毕设源码-钟学长17 小时前
【开题答辩全过程】以 儿童游泳预约系统为例,包含答辩的问题和答案
java·eclipse
蹦跑的蜗牛18 小时前
Spring Boot 使用 Redis 实现消息队列
spring boot·1024程序员节
清空mega18 小时前
从零开始搭建 flask 博客实验(2)
后端·python·flask
皮皮林55118 小时前
5种接口频率监控方案实战对比,性能、成本、复杂度全解析!
java
似水流年 光阴已逝18 小时前
从Jar包到K8s上线:全流程拆解+高可用实战
java·kubernetes·jar