SpringBoot多数据源动态切换方案:AbstractRoutingDataSource详解

博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
DeepSeek-行业融合之万象视界(附实战案例详解100+)
全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
感兴趣的可以先收藏起来,希望帮助更多的人
在这里插入图片描述

SpringBoot 多数据源动态切换方案:AbstractRoutingDataSource 详解

一、引言

在现代企业级应用开发中,一个应用系统可能需要同时访问多个数据源,例如主从数据库架构、多租户系统等。Spring Boot 作为一个快速开发框架,提供了便捷的方式来处理多数据源问题。其中,AbstractRoutingDataSource 是 Spring 框架中用于动态切换数据源的核心类。本文将详细介绍如何使用 AbstractRoutingDataSource 实现 Spring Boot 多数据源动态切换方案。

二、多数据源需求背景

2.1 主从数据库架构

在高并发的应用场景中,为了提高数据库的读写性能,通常会采用主从数据库架构。主数据库负责写操作,从数据库负责读操作。应用程序需要根据不同的业务需求动态切换到主库或从库进行数据操作。

2.2 多租户系统

多租户系统是指多个租户共享同一个应用程序实例,但每个租户有自己独立的数据库。应用程序需要根据不同的租户信息动态切换到相应的数据库进行数据操作。

三、AbstractRoutingDataSource 原理

3.1 基本概念

AbstractRoutingDataSource 是 Spring 框架中的一个抽象类,它实现了 javax.sql.DataSource 接口,用于动态切换数据源。该类通过重写 determineCurrentLookupKey() 方法来确定当前使用的数据源。

3.2 工作流程

  1. 配置数据源:在 Spring Boot 应用中配置多个数据源,并将这些数据源存储在 AbstractRoutingDataSourcetargetDataSources 属性中。
  2. 设置默认数据源:通过 AbstractRoutingDataSourcedefaultTargetDataSource 属性设置默认数据源。
  3. 动态切换数据源:在业务代码中,通过调用 determineCurrentLookupKey() 方法返回当前使用的数据源的键,AbstractRoutingDataSource 根据该键从 targetDataSources 中获取相应的数据源。

四、Spring Boot 集成 AbstractRoutingDataSource 实现多数据源动态切换

4.1 项目搭建

首先,创建一个 Spring Boot 项目,添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jdbcartifactId>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
    dependency>
dependencies>

4.2 配置数据源

application.properties 中配置多个数据源:

# 主数据源
spring.datasource.master.url=jdbc:mysql://localhost:3306/master_db
spring.datasource.master.username=root
spring.datasource.master.password=root
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver

# 从数据源
spring.datasource.slave.url=jdbc:mysql://localhost:3306/slave_db
spring.datasource.slave.username=root
spring.datasource.slave.password=root
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver

4.3 创建数据源枚举类

创建一个枚举类来定义数据源的键:

public enum DataSourceType {
    MASTER, SLAVE
}

4.4 创建数据源上下文持有者

创建一个数据源上下文持有者类,用于在 ThreadLocal 中存储当前使用的数据源的键:

public class DataSourceContextHolder {
    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

    public static void setDataSource(DataSourceType dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static DataSourceType getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}

4.5 创建动态数据源类

创建一个继承自 AbstractRoutingDataSource 的动态数据源类,重写 determineCurrentLookupKey() 方法:

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

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

4.6 配置数据源 Bean

创建一个配置类,配置多个数据源和动态数据源:

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
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.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }
}

4.7 创建数据源切换注解

创建一个自定义注解,用于在业务方法上指定使用的数据源:

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceSwitch {
    DataSourceType value() default DataSourceType.MASTER;
}

4.8 创建数据源切换切面

创建一个切面类,在业务方法执行前根据注解设置当前使用的数据源:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DataSourceSwitchAspect {

    @Before("@annotation(com.example.demo.DataSourceSwitch)")
    public void before(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSourceSwitch dataSourceSwitch = method.getAnnotation(DataSourceSwitch.class);
        if (dataSourceSwitch != null) {
            DataSourceContextHolder.setDataSource(dataSourceSwitch.value());
        }
    }

    @After("@annotation(com.example.demo.DataSourceSwitch)")
    public void after(JoinPoint point) {
        DataSourceContextHolder.clearDataSource();
    }
}

4.9 使用数据源切换注解

在业务方法上使用 DataSourceSwitch 注解指定使用的数据源:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @DataSourceSwitch(DataSourceType.MASTER)
    public void insertUser(String name) {
        String sql = "INSERT INTO user (name) VALUES (?)";
        jdbcTemplate.update(sql, name);
    }

    @DataSourceSwitch(DataSourceType.SLAVE)
    public String getUserName(int id) {
        String sql = "SELECT name FROM user WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, String.class);
    }
}

五、注意事项

5.1 线程安全问题

由于 DataSourceContextHolder 使用了 ThreadLocal 来存储当前使用的数据源的键,因此在多线程环境下需要注意线程安全问题。确保在每个线程中正确设置和清除数据源信息。

5.2 事务管理

在使用多数据源时,需要注意事务管理的问题。如果一个业务方法涉及到多个数据源的操作,需要使用分布式事务管理方案,如 Seata 等。

六、总结

通过使用 AbstractRoutingDataSource,我们可以很方便地实现 Spring Boot 多数据源动态切换方案。本文详细介绍了 AbstractRoutingDataSource 的原理和使用方法,并给出了具体的代码示例。在实际应用中,我们可以根据业务需求灵活配置和使用多数据源,提高应用程序的性能和可扩展性。

你可能感兴趣的:(Web,spring,boot,后端,java)