当你的服务需要去连接很多个数据源的时候,需要这样配置。可以是那么支持嵌套的,和stack一样的效果;还可以是那种独立支持拉平的设计。
一、基本实现方式
- 配置多个数据源
spring:datasource:primary:url: jdbc:mysql://localhost:3306/db1username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driversecondary:url: jdbc:mysql://localhost:3306/db2username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
- 数据源配置类
@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();}
}
二、动态数据源实现
- 自定义数据源路由
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected 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();}
}
- 数据源配置
@Configuration
public class DynamicDataSourceConfig {@Bean@Primarypublic 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;}
}
- 注解方式切换数据源
@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();}}
}
三、实现原理解析
- 核心原理
public abstract class AbstractRoutingDataSource extends AbstractDataSource {private Map<Object, Object> targetDataSources;private Object defaultTargetDataSource;private Map<Object, DataSource> resolvedDataSources;@Overridepublic 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();
}
- 事务管理
@Configuration
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Beanpublic TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {return new TransactionTemplate(transactionManager);}
}
四、高级特性实现
- 读写分离实现
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());}}
}
- 数据源健康检查
@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) {// 实现数据源故障处理逻辑}
}
- 动态添加数据源
@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();}
}
五、性能优化与最佳实践
- 数据源连接池配置
@Configuration
public class DataSourcePoolConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.primary.hikari")public HikariConfig primaryHikariConfig() {return new HikariConfig();}@Beanpublic DataSource primaryDataSource(HikariConfig hikariConfig) {return new HikariDataSource(hikariConfig);}
}
- 监控与统计
@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);}});}
}
- 异常处理
@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存储当前数据源的标识,实现数据源的动态切换。在实际应用中,需要注意以下几点:
事务管理:确保在同一个事务中使用同一个数据源
性能优化:合理配置连接池参数
异常处理:妥善处理数据源切换和数据库操作异常
监控统计:实现数据源使用情况的监控
健康检查:定期检查数据源的可用性
通过合理的实现和配置,可以实现稳定、高效的多数据源应用。