如何基于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
相关产品推荐
相关产品推荐

