AbstractRoutingDataSource 适用场景详解

47 阅读3分钟

AbstractRoutingDataSource 适用场景详解

您已经列出了 AbstractRoutingDataSource 的主要适用场景,下面我为每个场景提供更详细的说明和实现思路:

1. 读写分离

实现思路:

  • 配置主数据源(负责写操作)和多个从数据源(负责读操作)
  • 使用 AOP 拦截 Service 层方法,根据方法名或注解判断是读还是写操作
  • 写操作设置数据源标识为主库,读操作设置为从库
  • 可以实现简单的负载均衡,从多个从库中选择一个

代码示例:

@Aspect
@Component
public class ReadWriteDataSourceAspect {
    // 拦截所有以query、find、get开头的方法,使用从库
    @Before("execution(* com.example.service.*.query*(*)) || execution(* com.example.service.*.find*(*)) || execution(* com.example.service.*.get*(*))")
    public void setReadDataSource() {
        DataSourceContextHolder.setDataSourceKey("slave");
    }
    
    // 拦截所有以insert、update、delete开头的方法,使用主库
    @Before("execution(* com.example.service.*.insert*(*)) || execution(* com.example.service.*.update*(*)) || execution(* com.example.service.*.delete*(*))")
    public void setWriteDataSource() {
        DataSourceContextHolder.setDataSourceKey("master");
    }
}

2. 多租户系统

实现思路:

  • 为每个租户配置独立的数据源(或数据库/模式)
  • 使用 ThreadLocal 存储当前请求的租户标识
  • 实现 determineCurrentLookupKey() 方法,返回当前租户对应的数据源标识
  • 可以结合过滤器或拦截器,从请求头或参数中获取租户信息

关键实现:

public class TenantContextHolder {
    private static final ThreadLocal<String> tenantThreadLocal = new ThreadLocal<>();
    
    public static void setTenantId(String tenantId) {
        tenantThreadLocal.set(tenantId);
    }
    
    public static String getTenantId() {
        return tenantThreadLocal.get();
    }
    
    public static void clear() {
        tenantThreadLocal.remove();
    }
}

public class TenantRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TenantContextHolder.getTenantId();
    }
}

3. 分库分表

实现思路:

  • 根据业务规则(如用户ID、订单ID)计算出对应的数据库分片
  • 在执行操作前设置目标数据源标识
  • 可以实现一致性哈希算法进行分片路由

应用示例:

public class ShardingRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 从分片上下文获取分片键
        Long shardKey = ShardContextHolder.getShardKey();
        if (shardKey == null) {
            return "defaultDataSource";
        }
        // 简单的分片算法:取模分片
        int shardIndex = (int)(Math.abs(shardKey) % 4); // 假设有4个分片
        return "dataSource_" + shardIndex;
    }
}

4. 多数据库类型混合

实现思路:

  • 配置多种不同类型的数据源(如MySQL、PostgreSQL、Oracle等)
  • 根据业务模块或操作类型选择适合的数据库
  • 需要注意不同数据库的SQL语法差异

配置示例:

@Configuration
public class MultiDbConfig {
    @Bean("mysqlDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.mysql")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean("postgresqlDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.postgresql")
    public DataSource postgresqlDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean("dynamicDataSource")
    @Primary
    public DataSource dynamicDataSource(
            @Qualifier("mysqlDataSource") DataSource mysqlDataSource,
            @Qualifier("postgresqlDataSource") DataSource postgresqlDataSource) {
        DynamicDataSource dataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("mysql", mysqlDataSource);
        targetDataSources.put("postgresql", postgresqlDataSource);
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(mysqlDataSource);
        return dataSource;
    }
}

5. 动态数据源管理

实现思路:

  • 维护一个可动态更新的数据源映射
  • 提供API用于运行时添加或移除数据源
  • 重新初始化AbstractRoutingDataSource以应用新的数据源配置

动态管理实现:

@Component
public class DynamicDataSourceManager {
    @Autowired
    private DynamicDataSource dynamicDataSource;
    
    // 添加新数据源
    public void addDataSource(String dataSourceKey, DataSource dataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>(dynamicDataSource.getTargetDataSources());
        targetDataSources.put(dataSourceKey, dataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        // 重新初始化以应用更改
        dynamicDataSource.afterPropertiesSet();
    }
    
    // 移除数据源
    public void removeDataSource(String dataSourceKey) {
        Map<Object, Object> targetDataSources = new HashMap<>(dynamicDataSource.getTargetDataSources());
        targetDataSources.remove(dataSourceKey);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.afterPropertiesSet();
    }
}

实现中的注意事项

  1. 事务管理:确保在同一个事务中的所有操作使用同一个数据源
  2. 数据源缓存:考虑缓存频繁使用的数据源连接,提高性能
  3. 异常处理:添加数据源不可用的容错机制
  4. 监控与日志:记录数据源切换情况,便于问题排查
  5. 性能优化:避免过于频繁的数据源切换,可能影响性能

这些场景在实际应用中可以根据具体需求进行组合和扩展,AbstractRoutingDataSource 为动态数据源管理提供了灵活的基础框架。