Spring多数据源实现原理与实践
当你的服务需要去连接很多个数据源的时候,需要这样配置。可以是那么支持嵌套的,和stack一样的效果;还可以是那种独立支持拉平的设计。
一、基本实现方式
- 配置多个数据源
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
- 数据源配置类
@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 {
@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();
}
}
- 数据源配置
@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;
}
}
- 注解方式切换数据源
@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;
@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();
}
- 事务管理
@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);
}
}
四、高级特性实现
- 读写分离实现
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();
}
@Bean
public 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存储当前数据源的标识,实现数据源的动态切换。在实际应用中,需要注意以下几点:
事务管理:确保在同一个事务中使用同一个数据源
性能优化:合理配置连接池参数
异常处理:妥善处理数据源切换和数据库操作异常
监控统计:实现数据源使用情况的监控
健康检查:定期检查数据源的可用性
通过合理的实现和配置,可以实现稳定、高效的多数据源应用。
本文来自博客园,作者:Eular,转载请注明原文链接:https://www.cnblogs.com/euler-blog/p/18786407
浙公网安备 33010602011771号