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();
}
}
实现中的注意事项
- 事务管理:确保在同一个事务中的所有操作使用同一个数据源
- 数据源缓存:考虑缓存频繁使用的数据源连接,提高性能
- 异常处理:添加数据源不可用的容错机制
- 监控与日志:记录数据源切换情况,便于问题排查
- 性能优化:避免过于频繁的数据源切换,可能影响性能
这些场景在实际应用中可以根据具体需求进行组合和扩展,AbstractRoutingDataSource 为动态数据源管理提供了灵活的基础框架。