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

发布于:2024-12-23 ⋅ 阅读:(11) ⋅ 点赞:(0)

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

下一篇博客我们将结合最佳实践来揭秘dynamic-datasource-spring-boot-starter组件的使用。