文章目录
- 动态数据源实现方式
- 1. 概述
- 2. 动态数据源实现方式
- 2.1 基于 `AbstractRoutingDataSource` 实现动态数据源
- 2.2 基于 Spring AOP 实现动态数据源
- 2.3 基于 `TransactionManager` 实现动态数据源
- 2.4 通过数据库中间件实现动态数据源(ShardingSphere、MyCAT)
- 3. 各实现方式对比
- 4. 总结
动态数据源实现方式
1. 概述
动态数据源是现代企业级应用中常见的需求,尤其是在 多数据源管理、读写分离、多租户系统、分库分表 等场景中,动态切换不同的数据源可以提升系统的灵活性和性能。本文将详细介绍几种常见的动态数据源实现方式,包括其应用场景、实现步骤、优缺点对比。
2. 动态数据源实现方式
2.1 基于 AbstractRoutingDataSource
实现动态数据源
适用场景:
主要适用于需要根据业务上下文动态切换数据源的场景,常用于 读写分离、多租户系统 等应用。
实现原理:
Spring 框架提供的 AbstractRoutingDataSource
类支持动态数据源路由。通过继承该类并重写 determineCurrentLookupKey()
方法,基于当前线程上下文中的数据源标识符进行数据源切换。
实现步骤:
- 创建数据源配置类:定义多个数据源,并将它们注入
DynamicDataSource
。
@Configuration
public class DataSourceConfig {@Bean(name = "dataSource1")@ConfigurationProperties(prefix = "spring.datasource.db1")public DataSource dataSource1() {return DataSourceBuilder.create().build();}@Bean(name = "dataSource2")@ConfigurationProperties(prefix = "spring.datasource.db2")public DataSource dataSource2() {return DataSourceBuilder.create().build();}@Primary@Bean(name = "dynamicDataSource")public DataSource dynamicDataSource(@Qualifier("dataSource1") DataSource dataSource1,@Qualifier("dataSource2") DataSource dataSource2) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("db1", dataSource1);targetDataSources.put("db2", dataSource2);DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(dataSource1);return dynamicDataSource;}
}
- 创建
DynamicDataSourceContextHolder
:使用ThreadLocal
管理数据源标识。
public class DynamicDataSourceContextHolder {private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();public static void setDataSourceType(String dataSourceType) {CONTEXT_HOLDER.set(dataSourceType);}public static String getDataSourceType() {return CONTEXT_HOLDER.get();}public static void clearDataSourceType() {CONTEXT_HOLDER.remove();}
}
- 创建
DynamicDataSource
类:继承AbstractRoutingDataSource
并重写determineCurrentLookupKey()
方法。
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}
- 使用 AOP 切面动态切换数据源:
@Aspect
@Component
public class DynamicDataSourceAspect {@Before("@annotation(ds)")public void switchDataSource(JoinPoint point, DataSource ds) {DynamicDataSourceContextHolder.setDataSourceType(ds.value());}@After("@annotation(ds)")public void clearDataSource(JoinPoint point, DataSource ds) {DynamicDataSourceContextHolder.clearDataSourceType();}
}
- 使用自定义注解
@DataSource
指定数据源:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value() default "db1";
}
优点:
- 实现简单,轻松与 Spring 框架集成。
- 适用于小型项目的动态数据源需求。
缺点:
- 性能可能在复杂场景中遇到瓶颈。
2.2 基于 Spring AOP 实现动态数据源
适用场景:
适合需要细粒度动态切换数据源的场景,比如特定方法或业务逻辑需要在不同的数据源中执行。
实现原理:
通过 Spring AOP 技术,在方法执行前后拦截调用,根据自定义注解切换数据源。
实现步骤:
- 创建自定义注解
@DataSource
:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value();
}
- 定义 AOP 切面类:
@Aspect
@Component
public class DynamicDataSourceAspect {@Before("@annotation(ds)")public void switchDataSource(JoinPoint point, DataSource ds) {DynamicDataSourceContextHolder.setDataSourceType(ds.value());}@After("@annotation(ds)")public void clearDataSource(JoinPoint point, DataSource ds) {DynamicDataSourceContextHolder.clearDataSourceType();}
}
- 在业务层使用
@DataSource
注解 切换数据源:
@Service
public class UserService {@DataSource("db1")public List<User> getAllUsersFromDb1() {// 从 db1 获取数据}@DataSource("db2")public List<User> getAllUsersFromDb2() {// 从 db2 获取数据}
}
优点:
- 实现灵活,支持细粒度的控制。
- 易于使用,适合小范围数据源切换。
缺点:
- 随着项目的规模增加,过多的切面可能导致维护复杂性上升。
2.3 基于 TransactionManager
实现动态数据源
适用场景:
用于需要在不同数据源之间切换并保持事务一致性的场景,特别适合有 多数据源事务管理 需求的应用。
实现原理:
通过为每个数据源配置不同的 TransactionManager
,在 @Transactional
注解中动态指定事务管理器,确保数据源切换时的事务一致性。
实现步骤:
- 配置每个数据源的
TransactionManager
:
@Bean
public PlatformTransactionManager transactionManager1(@Qualifier("dataSource1") DataSource dataSource1) {return new DataSourceTransactionManager(dataSource1);
}@Bean
public PlatformTransactionManager transactionManager2(@Qualifier("dataSource2") DataSource dataSource2) {return new DataSourceTransactionManager(dataSource2);
}
- 使用
@Transactional
注解指定事务管理器:
@Service
public class UserService {@Transactional("transactionManager1")public void performDb1Operation() {// 操作 db1}@Transactional("transactionManager2")public void performDb2Operation() {// 操作 db2}
}
优点:
- 支持多数据源的事务管理,保证数据一致性。
- 适合需要事务支持的场景。
缺点:
- 配置复杂,特别是涉及到分布式事务时。
2.4 通过数据库中间件实现动态数据源(ShardingSphere、MyCAT)
适用场景:
适用于 大规模分库分表、读写分离 等复杂场景,尤其是对性能有较高要求的项目。
实现原理:
使用数据库中间件(如 ShardingSphere、MyCAT),通过中间件实现数据源路由、分片、读写分离等功能。开发者不需要手动处理数据源切换,所有逻辑由中间件根据配置文件自动完成。
实现步骤:
- 引入 ShardingSphere 依赖:
<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-core</artifactId><version>x.x.x</version>
</dependency>
- 配置数据源和路由规则:
在 application.yml
中配置数据源和分片规则。
sharding:data-sources:ds_0:url: jdbc:mysql://localhost:3306/db0username: rootpassword: rootds_1:url: jdbc:mysql://localhost:3306/db1username: rootpassword: root
优点:
- 中间件提供高性能支持,适合复杂的大规模场景。
- 减少了开发者手动管理多数据源的工作量。
缺点:
- 学习成本较高,配置复杂。
- 对中间件的稳定性有较高的依赖。
3. 各实现方式对比
实现方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
AbstractRoutingDataSource | 读写分离、多租户 | 简单易用,与 Spring 框架无缝集成 | 在复杂场景中可能存在性能瓶颈 |
基于 Spring AOP | 细粒度数据源切换 | 灵活,支持特定方法和类的动态切换 | 过多切面可能增加调试和维护难度 |
基于 TransactionManager | 多数据源事务管理 | 保证多数据源场景中的事务一致性 | 配置复杂,特别是分布式事务时 |
数据库中间件(ShardingSphere等) | 分库分表、读写分离 | 性能优异,适合复杂场景,开发者不需手动处理 | 学习成本高,依赖中间件性能和稳定性 |
手动切换数据源 | 特殊业务场景 | 完全控制数据源切换,灵活 | 实现复杂,维护成本高 |
4. 总结
动态数据源技术为企业级应用提供了灵活的数据库管理能力。不同的实现方式各有优劣,开发者需要根据项目规模、业务需求和性能要求进行选择。
- 小型项目:可以选择基于
AbstractRoutingDataSource
或 Spring AOP 的实现,简单易用,能够快速满足动态切换需求。 - 需要事务管理:对于涉及多数据源事务管理的项目,基于
TransactionManager
的方式更为适合,可以保证数据一致性。 - 大规模项目:如果是分库分表或读写分离场景,数据库中间件(如 ShardingSphere)能够提供高性能支持。
- 特殊需求:对于少数特殊业务场景,手动切换数据源可能是最灵活的方案,但维护成本较高。
通过合理的动态数据源选择与实现,能够显著提升系统的扩展性和性能,满足不同复杂业务场景的需求。