使用AbstractRoutingDataSource实现数据源的动态切换

AbstractRoutingDataSource 是 Spring 框架提供的一个抽象类,用于实现动态数据源路由。这个类主要用于多数据源场景,其中可以根据不同的条件动态地切换到不同的数据源。

实现原理

AbstractRoutingDataSource 继承自 Spring 的 AbstractDataSource 类,并实现了 DataSource 接口。它内部维护了一个映射(Map),用于存储数据源标识和对应的数据源实例。当需要获取连接时,AbstractRoutingDataSource 会调用一个抽象方法 determineCurrentLookupKey() 来确定当前的数据源标识,然后根据这个标识从映射中获取对应的数据源,并从该数据源中获取连接。

关键方法解析

  1. determineCurrentLookupKey(): 这是一个抽象方法,需要用户自己实现。这个方法用于确定当前的数据源标识,其返回值将用作查找映射中对应数据源的关键字。通常,可以在这个方法中获取当前线程的一些状态或上下文信息,来动态决定使用哪个数据源。

  2. afterPropertiesSet() : 这个方法是在设置完所有属性后被调用的。在这个方法中,AbstractRoutingDataSource 会验证数据源映射是否已经被正确设置,并且如果设置了默认数据源,也会进行检查。

  3. getConnection()getConnection(String username, String password) : 这两个方法是 DataSource 接口要求实现的方法。在 AbstractRoutingDataSource 中,这两个方法的实现逻辑是先调用 determineCurrentLookupKey() 方法获取当前数据源标识,然后根据这个标识从映射中找到对应的数据源,并调用该数据源的 getConnection() 方法获取连接。

场景实例

我们设计一个这样的场景,比如一个多租户(multi-tenant)应用,其中每个租户都有自己的数据库。在这个场景中,系统根据当前用户的租户ID动态切换数据源。我们将使用 Spring Boot、Spring Data JPA 和 AbstractRoutingDataSource 来实现这个场景。

步骤 1: 定义租户识别逻辑

首先,我们需要一个机制来识别当前请求属于哪个租户。假设每个请求的HTTP头部都包含一个X-TenantID字段来标识租户ID:

java 复制代码
@Component
public class TenantInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String tenantId = request.getHeader("X-TenantID");
        if (tenantId != null) {
            TenantContext.setCurrentTenant(tenantId);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        TenantContext.clear();
    }
}

在这里,TenantContext是一个存储当前线程租户ID的类:

java 复制代码
public class TenantContext {
    private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();

    public static void setCurrentTenant(String tenantId) {
        currentTenant.set(tenantId);
    }

    public static String getCurrentTenant() {
        return currentTenant.get();
    }

    public static void clear() {
        currentTenant.remove();
    }
}

步骤 2: 配置动态数据源

接下来,我们需要配置动态数据源来根据租户ID选择正确的数据库:

java 复制代码
public class MultiTenantDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContext.getCurrentTenant();
    }
}

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        MultiTenantDataSource dataSource = new MultiTenantDataSource();

        Map<Object, Object> targetDataSources = new HashMap<>();
        // 假设已经有一个方法来获取所有租户的数据源配置
        Map<String, DataSource> tenantDataSources = tenantDataSources();
        targetDataSources.putAll(tenantDataSources);

        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(defaultDataSource()); // 默认数据源
        dataSource.afterPropertiesSet();
        return dataSource;
    }

    private Map<String, DataSource> tenantDataSources() {
        // 这里应该从配置文件或数据库中加载所有租户的数据源配置
        // 举例,假设有两个租户tenantA和tenantB
        Map<String, DataSource> result = new HashMap<>();
        result.put("tenantA", createDataSource("jdbc:mysql://localhost:3306/tenantA"));
        result.put("tenantB", createDataSource("jdbc:mysql://localhost:3306/tenantB"));
        return result;
    }

    private DataSource createDataSource(String url) {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl(url);
        dataSource.setUsername("username");
        dataSource.setPassword("password");
        return dataSource;
    }

    private DataSource defaultDataSource() {
        // 定义默认数据源
        return createDataSource("jdbc:mysql://localhost:3306/defaultDb");
    }
}

步骤 3: 集成到Spring MVC

最后,我们需要确保每个请求都会通过我们的TenantInterceptor,以便正确设置租户上下文:

java 复制代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private TenantInterceptor tenantInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tenantInterceptor);
    }
}
相关推荐
newerp9 分钟前
Redis 操作与缓存策略
后端·程序员·go
newerp12 分钟前
GORM ORM 基础
后端·程序员·go
newerp24 分钟前
CRUD 操作与预处理语句
后端·程序员·go
tachibana230 分钟前
把RAGAS跑起来
数据库·人工智能·ai·架构·大模型·llm·rag
蒸蒸yyyyzwd41 分钟前
cpp选手秋招学习笔记day14
数据库·oracle
数据库小学妹1 小时前
MySQL 8.4还是9.7 LTS?8.0 EOL后升级怎么选
数据库·mysql·dba·版本升级·数据库运维
存在morning1 小时前
【PySpark 学习笔记 三】DataFrame API 入门
android·java·数据库
m0_547486661 小时前
《数据库原理及应用教程MySQL 8.0》全套PPT课件2026
数据库·mysql
程序员夏洛1 小时前
MySQL 中的事务隔离级别有哪些?
数据库·mysql
冰暮流星1 小时前
mysql之字符串函数
数据库·mysql