Spring Boot 动态数据源与多线程共享的实现

在现代的微服务架构下,动态数据源的管理变得越来越重要。特别是当我们需要根据不同的条件(比如用户、请求类型等)来选择数据源时,动态数据源的灵活性更为突出。此外,在多线程环境下共享这些数据源也显得尤为重要。本文将介绍如何在 Spring Boot 中实现动态数据源与多线程共享的功能。

整体流程

为了实现动态数据源及其多线程共享,以下是整个过程中的主要步骤和实现目标:

步骤描述
1. 创建数据源配置类配置数据源、连接池等信息
2. 创建动态数据源路由类通过 ThreadLocal 保存当前线程的数据源标识
3. 创建切面类拦截请求,动态切换数据源
4. 在业务逻辑中实现在多线程环境中使用动态数据源

接下来,让我们逐步实现这些步骤。

步骤详解

1. 创建数据源配置类

首先,我们需要创建一个 DataSourceConfig 类来配置我们的数据源。

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

// 注解,标识为配置类
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceConfig {

    // 主数据源
    @Bean
    @Primary
    public DataSource primaryDataSource() {
        // 配置主数据源的相关信息
    }

    // 备用数据源
    @Bean
    public DataSource secondaryDataSource() {
        // 配置备用数据源的相关信息
    }

    @Bean
    public AbstractRoutingDataSource routingDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
                                                       @Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
        AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
            @Override
            protected Object determineCurrentLookupKey() {
                // 通过 ThreadLocal 获取当前数据源的标识
                return DataSourceContextHolder.getDataSourceType();
            }
        };
        // 设置主备用数据源
        Map<Object, Object> targetDataSources = Map.of("primary", primaryDataSource, "secondary", secondaryDataSource);
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
2. 创建动态数据源路由类

接下来,我们需要一个 DataSourceContextHolder 类,其中使用 ThreadLocal 来存储当前线程的数据源标识。

public class DataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceType(String dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
3. 创建切面类

为了动态切换数据源,我们需要创建一个切面类。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(DataSource) && @annotation(dataSourceAnnotation)")
    public void changeDataSource(DataSource dataSourceAnnotation) {
        // 根据注解的信息切换数据源
        DataSourceContextHolder.setDataSourceType(dataSourceAnnotation.value());
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
4. 在业务逻辑中实现

最后,在我们的业务逻辑中,我们需要使用调度程序来运行多线程,并调用不同的数据源。

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Async
    public void usePrimaryDataSource() {
        DataSourceContextHolder.setDataSourceType("primary");
        // 调用 CRUD 操作
        DataSourceContextHolder.clearDataSourceType();
    }

    @Async
    public void useSecondaryDataSource() {
        DataSourceContextHolder.setDataSourceType("secondary");
        // 调用 CRUD 操作
        DataSourceContextHolder.clearDataSourceType();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

类图示例

下面是该实现的类图,用 mermaid 语法表示:

DataSourceConfig +DataSource primaryDataSource() +DataSource secondaryDataSource() +AbstractRoutingDataSource routingDataSource(DataSource primary, DataSource secondary) DataSourceContextHolder +setDataSourceType(String type) +getDataSourceType() : String +clearDataSourceType() DataSourceAspect +changeDataSource(DataSource dataSourceAnnotation) UserService +void usePrimaryDataSource() +void useSecondaryDataSource()

结论

通过以上步骤,我们成功地实现了Spring Boot中的动态数据源和多线程共享。确保在使用动态数据源时,一定要在切面中正确切换数据源,并在业务逻辑中适时清除 ThreadLocal 中的数据源类型。希望这篇文章能帮助你更好地理解和实践这一功能!如果今后在这方面有更多的需求或问题,随时欢迎交流!