You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于AbstractRoutingDataSource扩展Spring JDBC Template实现多数据源?

适配Spring JDBC Template实现多租户多数据源

你已经用AbstractRoutingDataSource实现了JPA的多数据源路由,其实这套方案完全可以复用在Spring JDBC Template上——因为JDBC Template本质也是基于DataSource工作的,核心逻辑还是通过租户标识动态切换数据源。下面是具体的实现示例和注意事项:

1. 复用现有的租户路由数据源实现

直接沿用你已有的租户上下文Holder和自定义路由数据源即可,无需重新开发:

// 租户上下文Holder,线程安全存储当前租户ID
public class TenantContextHolder {
    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();
    }
}

// 自定义路由数据源,继承AbstractRoutingDataSource
public class TenantRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 从上下文获取当前租户ID作为数据源匹配键
        return TenantContextHolder.getCurrentTenant();
    }
}

2. 配置JDBC Template使用路由数据源

在Spring配置类中,将JDBC Template的数据源指向你的TenantRoutingDataSource,而非单个固定数据源:

@Configuration
public class DataSourceConfig {

    // 配置租户A的数据源
    @Bean
    public DataSource tenantADataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/tenant_a");
        dataSource.setUsername("user_a");
        dataSource.setPassword("pass_a");
        return dataSource;
    }

    // 配置租户B的数据源
    @Bean
    public DataSource tenantBDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/tenant_b");
        dataSource.setUsername("user_b");
        dataSource.setPassword("pass_b");
        return dataSource;
    }

    // 配置路由数据源,绑定租户ID与对应数据源的映射
    @Bean
    public DataSource routingDataSource() {
        TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("tenant_a", tenantADataSource());
        targetDataSources.put("tenant_b", tenantBDataSource());
        
        routingDataSource.setTargetDataSources(targetDataSources);
        // 设置默认数据源(可选,当租户ID不存在时使用)
        routingDataSource.setDefaultTargetDataSource(tenantADataSource());
        return routingDataSource;
    }

    // 注入路由数据源到JDBC Template
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(routingDataSource());
    }
}

3. 业务层使用JDBC Template

在业务代码中,只需先设置当前租户ID,再调用JDBC Template的方法即可自动切换数据源:

@Service
public class TenantDataService {
    private final JdbcTemplate jdbcTemplate;

    public TenantDataService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<Map<String, Object>> fetchTenantData(String tenantId) {
        // 设置当前租户ID
        TenantContextHolder.setCurrentTenant(tenantId);
        try {
            // JDBC Template会自动使用对应租户的数据源执行SQL
            return jdbcTemplate.queryForList("SELECT * FROM business_table");
        } finally {
            // 必须清理上下文,避免线程池复用导致租户信息泄漏
            TenantContextHolder.clear();
        }
    }
}

4. 关键注意事项

  • 线程安全:一定要在finally块中调用TenantContextHolder.clear(),或者用AOP切面统一处理租户ID的设置与清理,防止线程池复用引发的租户数据混乱。
  • 动态数据源扩展:如果需要动态添加租户数据源,可以将targetDataSources改为ConcurrentHashMap,并在TenantRoutingDataSource中提供添加数据源的方法,调用setTargetDataSources()刷新路由配置。
  • AOP优化:可以通过AOP切面在请求入口(比如Controller层)自动解析租户ID(从请求头、Token等)并设置到上下文,减少业务代码的重复操作。

内容的提问来源于stack exchange,提问作者Abhilash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 18:25:23