Quantcast
Channel: 小蓝博客
Viewing all articles
Browse latest Browse all 3145

SpringBoot项目中实现数据源与数据库动态切换

$
0
0

下面介绍如何在 SpringBoot 项目中实现 数据源数据库动态切换。本文将详细解析核心原理、关键组件及代码实现步骤,通过示例代码、注释说明以及思维导图,帮助您快速掌握该技术方案。😊


一、原理概述

在多数据源应用场景中,不同的业务需要访问不同的数据库。利用 动态数据源 技术,可以在运行时根据业务需求切换数据源,避免硬编码数据源配置,提升系统扩展性。实现动态切换的核心思路是:

  1. 通过 AbstractRoutingDataSource 抽象类创建自定义数据源,并重写 determineCurrentLookupKey 方法获取当前数据源的标识。
  2. 使用 ThreadLocal 存储数据源标识,实现线程级别的数据源切换。
  3. 通过 AOP 或自定义注解,在业务方法调用前后切换数据源。

二、关键组件及工作流程

组件功能说明
DataSourceContextHolder利用 ThreadLocal 存储当前数据源标识保证同一线程内数据源的一致性
DynamicDataSource继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey 方法根据 DataSourceContextHolder 返回数据源 key
自定义注解标记需要切换数据源的方法配合 AOP 在方法执行前后进行数据源设置与清理
DataSourceAspectAOP 切面,拦截带有自定义注解的方法,实现数据源切换在方法调用前设置数据源,执行完毕后恢复默认数据源

工作流程图:

flowchart TD
    A[<span style="color: red;">业务方法调用</span>]
    B[<span style="color: red;">AOP 拦截</span>]
    C[<span style="color: red;">设置数据源(ThreadLocal)</span>]
    D[<span style="color: red;">调用 DynamicDataSource.determineCurrentLookupKey()</span>]
    E[<span style="color: red;">选择对应数据源</span>]
    F[<span style="color: red;">执行业务逻辑</span>]
    G[<span style="color: red;">清除 ThreadLocal 数据</span>]

    A --> B
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G

说明:

  • AOP 拦截:通过自定义注解在方法调用前,设置当前数据源标识;
  • DynamicDataSource 根据 ThreadLocal 中存储的 key 返回对应的数据源;
  • 业务方法执行完后,清除 ThreadLocal 信息,防止数据污染。

三、实战代码示例

1. DataSourceContextHolder

package com.example.datasource;

public class DataSourceContextHolder {
    // 使用 ThreadLocal 存储数据源标识,保证线程安全
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置数据源标识
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }

    // 获取当前数据源标识
    public static String getDataSourceKey() {
        return contextHolder.get();
    }

    // 清除数据源标识
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }
}

代码解析:

  • ThreadLocal 用于存储当前线程数据源标识,避免多线程数据冲突;
  • 提供 setgetclear 三个基本操作,方便在 AOP 中进行调用。

2. DynamicDataSource

package com.example.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    // 重写 determineCurrentLookupKey 方法,返回当前数据源标识
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceKey();
    }
}

代码解析:

  • 继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey 方法,返回当前线程设置的数据源 key,从而动态选择数据源。

3. 自定义注解与 AOP 切面

自定义注解

package com.example.datasource;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    // 指定数据源的名称,如 "master"、"slave" 等
    String value() default "master";
}

代码解析:

  • @DataSource 注解用于标记需要切换数据源的方法或类,value 属性指定目标数据源名称。

AOP 切面

package com.example.datasource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Order(-1)  // 保证在事务之前执行
@Component
public class DataSourceAspect {

    @Around("@annotation(com.example.datasource.DataSource)")
    public Object switchDataSource(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        // 设置当前数据源,使用注解中的 value 值
        DataSourceContextHolder.setDataSourceKey(dataSource.value());
        try {
            // 执行目标方法
            return point.proceed();
        } finally {
            // 方法执行完毕后清除数据源设置,防止影响其他线程
            DataSourceContextHolder.clearDataSourceKey();
        }
    }
}

代码解析:

  • @Around 拦截带有 @DataSource 注解的方法;
  • 在方法执行前调用 DataSourceContextHolder.setDataSourceKey 设置数据源;
  • 方法执行后,通过 clearDataSourceKey 清理 ThreadLocal 数据,保证后续调用不受影响。

四、Spring 配置

在 Spring 配置中,将多个数据源配置好,并将 DynamicDataSource 注入到数据源 Bean 中:

package com.example.config;

import com.example.datasource.DynamicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

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

@Configuration
public class DataSourceConfig {

    // 主数据源配置
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.master")
    public DataSourceProperties masterDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "masterDataSource")
    @Primary
    public DataSource masterDataSource() {
        return masterDataSourceProperties().initializeDataSourceBuilder().build();
    }

    // 从数据源配置
    @Bean
    @ConfigurationProperties("spring.datasource.slave")
    public DataSourceProperties slaveDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "slaveDataSource")
    public DataSource slaveDataSource() {
        return slaveDataSourceProperties().initializeDataSourceBuilder().build();
    }

    // 配置动态数据源
    @Bean
    public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("slave", slaveDataSource);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }

    // 配置事务管理器,确保事务支持动态数据源
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}

代码解析:

  • 分别配置主数据源(master)与从数据源(slave),利用 @ConfigurationProperties 自动注入配置;
  • 将两个数据源封装到 DynamicDataSource 中,并设置默认数据源为 master;
  • 配置事务管理器,确保事务在动态数据源环境下正常运行。

五、总结

通过以上步骤,我们实现了在 SpringBoot 项目中对 数据源数据库动态切换 的完整解决方案。关键优势包括:

  • 灵活性:根据业务需求动态切换数据源,支持多种数据库配置;
  • 线程安全:利用 ThreadLocal 确保同一线程内数据源一致;
  • 便捷性:自定义注解与 AOP 切面让切换过程透明,业务代码无需额外处理。

希望本文通过详细的代码示例与流程图,能为您提供 实战指导,助力构建高性能、可扩展的数据访问层。💡


Viewing all articles
Browse latest Browse all 3145

Trending Articles