Spring多数据源实现原理与实践

当你的服务需要去连接很多个数据源的时候,需要这样配置。可以是那么支持嵌套的,和stack一样的效果;还可以是那种独立支持拉平的设计。

一、基本实现方式

  1. 配置多个数据源
spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/db1
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      url: jdbc:mysql://localhost:3306/db2
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
  1. 数据源配置类
@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

二、动态数据源实现

  1. 自定义数据源路由
public class DynamicDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }
    
    public static String getDataSourceType() {
        return contextHolder.get();
    }
    
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}
  1. 数据源配置
@Configuration
public class DynamicDataSourceConfig {
    
    @Bean
    @Primary
    public DataSource dynamicDataSource(
            @Qualifier("primaryDataSource") DataSource primaryDataSource,
            @Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
        
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("primary", primaryDataSource);
        targetDataSources.put("secondary", secondaryDataSource);
        
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        
        return dynamicDataSource;
    }
}
  1. 注解方式切换数据源
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "primary";
}

@Aspect
@Component
public class DataSourceAspect {
    
    @Pointcut("@annotation(com.example.DataSource)")
    public void dataSourcePointcut() {}
    
    @Around("dataSourcePointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        
        DataSource ds = method.getAnnotation(DataSource.class);
        if (ds == null) {
            ds = point.getTarget().getClass().getAnnotation(DataSource.class);
        }
        
        if (ds != null) {
            DataSourceContextHolder.setDataSourceType(ds.value());
        }
        
        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.clearDataSourceType();
        }
    }
}

三、实现原理解析

  1. 核心原理
public abstract class AbstractRoutingDataSource extends AbstractDataSource {
    
    private Map<Object, Object> targetDataSources;
    private Object defaultTargetDataSource;
    private Map<Object, DataSource> resolvedDataSources;
    
    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }
    
    protected DataSource determineTargetDataSource() {
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = resolvedDataSources.get(lookupKey);
        if (dataSource == null) {
            dataSource = (DataSource) defaultTargetDataSource;
        }
        return dataSource;
    }
    
    protected abstract Object determineCurrentLookupKey();
}
  1. 事务管理
@Configuration
public class TransactionConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(
            @Qualifier("dynamicDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    
    @Bean
    public TransactionTemplate transactionTemplate(
            PlatformTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }
}

四、高级特性实现

  1. 读写分离实现
public enum DataSourceType {
    MASTER,
    SLAVE
}

@Aspect
@Component
public class ReadWriteSplitAspect {
    
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalPointcut() {}
    
    @Before("transactionalPointcut()")
    public void before(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        
        Transactional transactional = method.getAnnotation(Transactional.class);
        if (transactional != null && transactional.readOnly()) {
            DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name());
        } else {
            DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
        }
    }
}
  1. 数据源健康检查
@Component
public class DataSourceHealthChecker {
    
    private final Map<String, DataSource> dataSources;
    
    public DataSourceHealthChecker(Map<String, DataSource> dataSources) {
        this.dataSources = dataSources;
    }
    
    @Scheduled(fixedRate = 60000)
    public void checkDataSourceHealth() {
        dataSources.forEach((name, dataSource) -> {
            try (Connection conn = dataSource.getConnection()) {
                try (Statement stmt = conn.createStatement()) {
                    stmt.execute("SELECT 1");
                }
                log.info("DataSource {} is healthy", name);
            } catch (SQLException e) {
                log.error("DataSource {} is unhealthy", name, e);
                // 处理不健康的数据源
                handleUnhealthyDataSource(name, dataSource);
            }
        });
    }
    
    private void handleUnhealthyDataSource(String name, DataSource dataSource) {
        // 实现数据源故障处理逻辑
    }
}

  1. 动态添加数据源
@Service
public class DynamicDataSourceManager {
    
    private final DynamicDataSource dynamicDataSource;
    
    public DynamicDataSourceManager(DynamicDataSource dynamicDataSource) {
        this.dynamicDataSource = dynamicDataSource;
    }
    
    public void addDataSource(String name, DataSource dataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>(
            dynamicDataSource.getTargetDataSources());
        targetDataSources.put(name, dataSource);
        
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.afterPropertiesSet();
    }
    
    public void removeDataSource(String name) {
        Map<Object, Object> targetDataSources = new HashMap<>(
            dynamicDataSource.getTargetDataSources());
        targetDataSources.remove(name);
        
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.afterPropertiesSet();
    }
}

五、性能优化与最佳实践

  1. 数据源连接池配置
@Configuration
public class DataSourcePoolConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary.hikari")
    public HikariConfig primaryHikariConfig() {
        return new HikariConfig();
    }
    
    @Bean
    public DataSource primaryDataSource(HikariConfig hikariConfig) {
        return new HikariDataSource(hikariConfig);
    }
}

  1. 监控与统计
@Component
public class DataSourceMetricsCollector {
    
    private final MeterRegistry registry;
    private final Map<String, DataSource> dataSources;
    
    public DataSourceMetricsCollector(MeterRegistry registry, 
                                    Map<String, DataSource> dataSources) {
        this.registry = registry;
        this.dataSources = dataSources;
        initializeMetrics();
    }
    
    private void initializeMetrics() {
        dataSources.forEach((name, dataSource) -> {
            if (dataSource instanceof HikariDataSource) {
                HikariDataSource hikariDS = (HikariDataSource) dataSource;
                
                Gauge.builder("hikaricp.connections.active", 
                            hikariDS, HikariDataSource::getActiveConnections)
                     .tag("pool", name)
                     .register(registry);
                
                Gauge.builder("hikaricp.connections.idle", 
                            hikariDS, HikariDataSource::getIdleConnections)
                     .tag("pool", name)
                     .register(registry);
            }
        });
    }
}
  1. 异常处理
@ControllerAdvice
public class DataSourceExceptionHandler {
    
    @ExceptionHandler(DataSourceLookupFailureException.class)
    public ResponseEntity<String> handleDataSourceLookupFailure(
            DataSourceLookupFailureException ex) {
        log.error("数据源查找失败", ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                           .body("数据源不可用");
    }
    
    @ExceptionHandler(SQLException.class)
    public ResponseEntity<String> handleSQLException(SQLException ex) {
        log.error("数据库操作失败", ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                           .body("数据库操作失败");
    }
}

多数据源的实现原理主要基于Spring的AbstractRoutingDataSource,通过ThreadLocal存储当前数据源的标识,实现数据源的动态切换。在实际应用中,需要注意以下几点:
事务管理:确保在同一个事务中使用同一个数据源
性能优化:合理配置连接池参数
异常处理:妥善处理数据源切换和数据库操作异常
监控统计:实现数据源使用情况的监控
健康检查:定期检查数据源的可用性
通过合理的实现和配置,可以实现稳定、高效的多数据源应用。

posted @ 2025-03-22 12:13  Eular  阅读(136)  评论(0)    收藏  举报