动态切换数据源原理

本文介绍了一种在不修改原有代码情况下,实现根据调用不同Service动态切换数据源的方法。利用ThreadLocal和Spring AOP技术,通过判断Service类的包名来自动设置数据源key,实现了灵活的数据源管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态切换数据源原理

有一种场景,调用ServiceA时,使用A数据源,调用ServiceB时,使用B数据源。在不改变原有代码的情况下,这个如何来实现呢。

下面是一种实现方式,供参考:

  • 将各个数据源用key-value形式保存到map中,如下面这个类:
class RouteDatasource {
	Map<String, Datasource> map = new HashMap<>();
	
	map.push("goods", goodsDatasource);
	map.push("order", orderDatasource);
	
	
	获取Connection
	public Connection getConnection() {
		...
	}

}
  • 在操作某个Service类的时候选择对应的数据源

比如在操作com.project.service.goods.GoodsService.java时,key应该设置成goods。这里有个问题是,service类跟数据源是分开的,怎么样才能通知到
RouteDatasource类使用goods的key呢。

方法简单,使用ThreadLocal类,在使用GoodsService类时,ThreadLocal中放入key,然后RouteDatasource类从ThreadLocal中取。

class RouteDatasource {
	Map<String, Datasource> map...
	
	map.push("goods", goodsDatasource);
	map.push("order", orderDatasource);
	
	获取Connection
	public Connection getConnection() {
		String key = ThreadLocal.get();
		Datasource ds = map.get(kay);
		return ds.getConnection();
	}

}

获取key完成了,如何设置key呢?不可能在每次使用前put一下吧。容易出错,代码也不美观。有一种方案是使用spring的AOP方式实现。

AOP可以在执行某个Service方法前做一些操作,那么我们就把设置key的操作放在AOP的before行为中。

大致思路是:判断当前方法所在类的package的名称来区分需要设置哪个key。

比如:com.project.service.goods.GoodsService.save()方法的package,是com.project.service.goods开头

那么AOP代码可以这样写

String methodInfo = ...
if(methodInfo.startWith("com.project.service.goods")) {
	ThreadLocal.set("goods")
}

完整的代码如下:

@Component
@Aspect
public class DynamicDataSourceAspect {
	
	@Before("execution(* com.project.service..*.*(..))")
	public void before(JoinPoint jp) {
		String methodName = jp.getSignature().getDeclaringTypeName();
		if (StringUtils.isBlank(methodName)) {
			return;
		}
		if (methodName.startsWith("com.project.service.goods")) {
			ThreadLocal.set("goods")
		} else if (methodName.startsWith("com.project.service.order")) {
			ThreadLocal.set("order")
		}

	}

}

这样,以后只需要配置DynamicDataSourceAspect类即可。

总结

数据源动态切换,主要思想是通过事先定义好的Key-Value进行动态获取。主要使用了ThreadLocal和Spring AOP方式去串连起来。

spring为我们提供了数据源切换类org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,这个类的作用就是一开始提到的RouteDatasource类。感兴趣的同学可以看下代码,很简单。

<think>好的,用户之前问过如何在Spring Boot中实现多数据源,现在他们想了解如何动态切换数据源。我需要先回顾之前的回答,确保这次的内容衔接上。动态切换数据源通常涉及运行时根据条件切换,比如根据不同的业务逻辑或租户切换不同的数据库。 首先,用户可能是在开发多租户应用,或者需要根据请求参数选择不同的数据库。他们可能已经配置了多个静态数据源,现在需要更灵活的动态切换。我需要考虑用户可能的场景,比如不同客户使用不同的数据库实例,或者分库分表的需求。 接下来,我需要整理动态切换的常见实现方法。常见的有使用AbstractRoutingDataSource结合AOP,或者第三方库如dynamic-datasource。之前提到过这些方案,现在需要详细说明如何实现。特别是AbstractRoutingDataSource的使用步骤,包括定义数据源路由键、配置动态数据源、定义切面等。 用户可能需要具体的代码示例,所以需要提供配置类和AOP的代码。同时,要注意事务管理的问题,动态切换时事务需要正确关联到对应的数据源,这点需要强调注意事项。此外,使用ThreadLocal来保存数据源键是常见做法,要确保线程安全。 还要考虑用户可能遇到的问题,比如数据源未正确切换、事务失效、性能问题等。在注意事项中需要提到连接池配置、避免跨事务切换、包路径隔离等。此外,第三方库dynamic-datasource可能更方便,用户可能想知道如何快速集成,所以需要简要介绍。 最后,需要确认回答结构清晰,步骤明确,代码示例正确,并且注意事项覆盖常见问题。同时,保持语言简洁,符合用户要求的格式,比如使用中文,使用适当的Markdown格式突出代码部分。</think>### Spring Boot 动态数据源切换实现指南 #### 一、核心实现原理 通过`AbstractRoutingDataSource` + ThreadLocal + AOP 实现动态路由,在运行时根据上下文动态选择数据源。核心组件: - **路由数据源**:继承`AbstractRoutingDataSource`实现`determineCurrentLookupKey()` - **上下文持有器**:使用`ThreadLocal`存储当前线程数据源标识 - **切面编程**:通过AOP在方法执行前设置数据源标识 #### 二、完整实现步骤 1. **配置文件(application.yml)** ```yaml spring: datasource: master: url: jdbc:mysql://localhost:3306/db1 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/db2 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver other: url: jdbc:postgresql://localhost:5432/db3 username: postgres password: 123456 driver-class-name: org.postgresql.Driver ``` 2. **定义数据源枚举** ```java public enum DataSourceType { MASTER, SLAVE, OTHER } ``` 3. **数据源上下文持有器** ```java public class DynamicDataSourceContextHolder { private static final ThreadLocal<DataSourceType> CONTEXT = new NamedThreadLocal<>("DynamicDataSourceContext"); public static void setDataSourceType(DataSourceType type) { CONTEXT.set(type); } public static DataSourceType getDataSourceType() { return CONTEXT.get() != null ? CONTEXT.get() : DataSourceType.MASTER; } public static void clear() { CONTEXT.remove(); } } ``` 4. **自定义路由数据源** ```java public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.other") public DataSource otherDataSource() { return DataSourceBuilder.create().build(); } @Bean public DataSource dynamicDataSource() { Map<Object, Object> targetDataSources = new HashMap<>(3); targetDataSources.put(DataSourceType.MASTER, masterDataSource()); targetDataSources.put(DataSourceType.SLAVE, slaveDataSource()); targetDataSources.put(DataSourceType.OTHER, otherDataSource()); DynamicDataSource ds = new DynamicDataSource(); ds.setDefaultTargetDataSource(masterDataSource()); ds.setTargetDataSources(targetDataSources); return ds; } } ``` 5. **AOP切面配置** ```java @Aspect @Component public class DataSourceAspect { @Pointcut("@annotation(com.example.annotation.DS)") public void dsPointcut() {} @Before("dsPointcut()") public void beforeExecute(JoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DS ds = method.getAnnotation(DS.class); DynamicDataSourceContextHolder.setDataSourceType(ds.value()); } @After("dsPointcut()") public void afterExecute() { DynamicDataSourceContextHolder.clear(); } } ``` 6. **自定义注解** ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DS { DataSourceType value() default DataSourceType.MASTER; } ``` 7. **事务管理器配置** ```java @Configuration @EnableTransactionManagement public class TransactionConfig { @Bean public PlatformTransactionManager transactionManager(DynamicDataSource dataSource) { return new DataSourceTransactionManager(dataSource); } } ``` #### 三、使用示例 ```java @Service public class OrderService { @DS(DataSourceType.MASTER) public void createOrder(Order order) { // 使用主库执行操作 } @DS(DataSourceType.SLAVE) public Order getOrder(Long id) { // 使用从库查询 return orderRepository.findById(id); } @DS(DataSourceType.OTHER) public void syncData() { // 使用其他数据源 } } ``` #### 四、注意事项 1. **线程安全问题**:必须使用`ThreadLocal`保证线程隔离 2. **事务管理**:建议在需要事务的方法上使用`@Transactional`注解 3. **连接池配置**:建议为每个数据源单独配置连接池 ```java @Bean @ConfigurationProperties(prefix = "spring.datasource.master.hikari") public DataSource masterDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } ``` 4. **清除上下文**:务必在切面中清理ThreadLocal数据避免内存泄漏 5. **失效场景**:不支持跨数据源事务,分布式事务需使用Seata等解决方案 #### 五、高级方案 1. **动态添加数据源**:通过`DataSourceRegistry`实现运行时注册新数据源 2. **自动路由策略**:根据表名、操作类型等自动选择数据源 3. **集成第三方库**:使用`dynamic-datasource-spring-boot-starter`简化配置 ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.0</version> </dependency> ``` #### 六、性能优化 1. 数据源预热:应用启动时建立初始连接 2. 连接池监控:集成Druid监控 3. 路由缓存:缓存数据源路由决策结果 > 通过这种实现方式,可以在不重启应用的情况下动态切换数据源,支持读写分离、多租户等场景。实际生产环境建议使用`dynamic-datasource`等成熟组件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值