/** * Copyright (c) 2018 人人开源 All rights reserved. * * https://www.renren.io * * 版权所有,侵权必究! */ package com.depower.datasource.config; import com.alibaba.druid.pool.DruidDataSource; import com.depower.datasource.properties.DataSourceProperties; import com.depower.datasource.properties.DynamicDataSourceProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * 配置多数据源 * * @author Mark [email protected] */ @Configuration @EnableConfigurationProperties(DynamicDataSourceProperties.class) public class DynamicDataSourceConfig { @Autowired private DynamicDataSourceProperties properties; @Bean @ConfigurationProperties(prefix = "spring.datasource.druid") public DataSourceProperties dataSourceProperties() { return new DataSourceProperties(); } @Bean public DynamicDataSource dynamicDataSource(DataSourceProperties dataSourceProperties) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(getDynamicDataSource()); //默认数据源 DruidDataSource defaultDataSource = DynamicDataSourceFactory.buildDruidDataSource(dataSourceProperties); dynamicDataSource.setDefaultTargetDataSource(defaultDataSource); return dynamicDataSource; } private Map<Object, Object> getDynamicDataSource(){ Map<String, DataSourceProperties> dataSourcePropertiesMap = properties.getDatasource(); Map<Object, Object> targetDataSources = new HashMap<>(dataSourcePropertiesMap.size()); dataSourcePropertiesMap.forEach((k, v) -> { DruidDataSource druidDataSource = DynamicDataSourceFactory.buildDruidDataSource(v); targetDataSources.put(k, druidDataSource); }); return targetDataSources; } }这个怎么撇换数据源
时间: 2025-08-26 16:54:55 浏览: 2
<think>首先,用户的问题是关于Spring Boot中如何切换多数据源,具体是询问“Spring Boot DynamicDataSource 如何切换数据源”。根据系统级指令,我需要使用中文回答,并确保回答结构清晰、真实可靠。
回顾提供的引用内容:
- 引用[1]:提到使用AbstractRoutingDataSource来实现动态选择数据源。
- 引用[2]:详细描述了使用Spring Boot实现动态数据源切换的步骤,包括配置数据源、实现动态数据源路由、定义数据源上下文和AOP切面。
- 引用[3]:展示了如何配置多个数据源的Bean。
- 引用[4]:给出了一个DynamicDataSource类的实现示例,使用@Primary注解和determineCurrentLookupKey方法。
我的回答需要基于这些引用,但确保回答真实可靠。我应该逐步解释如何实现数据源切换。
关键步骤:
1. **配置多个数据源**:在Spring Boot中定义多个数据源Bean。
2. **实现动态数据源路由**:创建一个类继承AbstractRoutingDataSource,并重写determineCurrentLookupKey方法。
3. **定义数据源上下文**:使用ThreadLocal来存储当前线程的数据源标识。
4. **使用AOP切面**:在方法执行前切换数据源,基于注解或其他机制。
5. **事务管理**:确保事务正确管理,因为数据源切换可能影响事务。
从引用[4]中,DynamicDataSource类示例:
- 它使用@Primary注解。
- 重写determineCurrentLookupKey方法,返回当前数据源的key。
- 在afterPropertiesSet方法中初始化数据源映射。
引用[2]提到定义数据源上下文和AOP切面。
回答结构:
- 先概述动态数据源切换的原理。
- 然后逐步解释实现步骤。
- 最后,生成相关问题。
确保LaTeX语法正确:在回答中,如果有数学表达式,使用$...$或$$...$$,但在这个上下文中,可能不需要,因为主题是Spring Boot配置。但如果有变量或代码相关,可以适当使用。
生成相关问题:
- 基于用户问题,生成2-3个相关问题。
- 例如:如何优化数据源切换?如何处理事务问题?等。
完整回答:
1. **介绍**:简要解释Spring Boot中动态数据源切换的概念。
2. **步骤**:详细描述实现步骤。
3. **代码示例**:提供关键代码片段,使用代码块。
4. **注意事项**:提醒事务管理等。
5. **相关问题**:生成相关问题。
最后,添加引用标识:在引用内容末尾添加[^n],但引用是来自用户提供的,所以需要适当引用它们。例如,在描述步骤时,引用相关部分。
用户提供的引用是:
- [^1]: 关于多数据源配置方案。
- [^2]: 关于动态数据源切换的步骤。
- [^3]: 关于配置数据源Bean。
- [^4]: 关于DynamicDataSource实现。
在回答中,我会自然地引用这些。</think>### Spring Boot 中 DynamicDataSource 如何切换数据源
在 Spring Boot 中,动态数据源切换(DynamicDataSource)允许应用在运行时根据业务需求(如租户隔离、读写分离)自动切换不同的数据源。核心实现基于 `AbstractRoutingDataSource` 类,它通过重写 `determineCurrentLookupKey()` 方法动态返回数据源的标识符(key),并结合线程本地存储(ThreadLocal)和 AOP 切面实现切换。下面我将逐步解释完整实现过程,确保回答真实可靠(基于 Spring Boot 官方文档和常见实践[^2][^4])。
#### 1. **配置多个数据源**
首先,在 `application.yml` 或 `application.properties` 中定义多个数据源(如主数据源和次数据源)。然后,通过 `@Configuration` 类创建对应的数据源 Bean。关键点:
- 使用 `@Primary` 注解指定默认数据源。
- 数据源类型可以是 `HikariCP`、`Druid` 等(这里以 `DataSourceBuilder` 为例)。
```java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
// 主数据源
@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
// 次数据源
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
```
- **说明**:`@ConfigurationProperties` 绑定配置文件中的属性(如 `spring.datasource.primary.url`)。确保在 `application.yml` 中配置对应属性:
```yaml
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/db1
username: root
password: root
secondary:
url: jdbc:mysql://localhost:3306/db2
username: root
password: root
```
引用[^3] 强调了此配置方法。
#### 2. **实现动态数据源路由**
创建 `DynamicDataSource` 类继承 `AbstractRoutingDataSource`,并重写 `determineCurrentLookupKey()` 方法。该方法返回当前线程应使用的数据源 key(如 "db1" 或 "db2")。
- 使用 `@Primary` 确保 Spring 优先使用此数据源。
- 在 `afterPropertiesSet()` 方法中初始化数据源映射。
```java
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
@Resource(name = "primaryDataSource")
private DataSource primaryDataSource;
@Resource(name = "secondaryDataSource")
private DataSource secondaryDataSource;
@Override
protected Object determineCurrentLookupKey() {
// 从数据源上下文中获取当前 key
return DataSourceContextHolder.getDataSourceKey();
}
@Override
public void afterPropertiesSet() {
// 初始化数据源映射
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("db1", primaryDataSource); // key 对应数据源 Bean
targetDataSources.put("db2", secondaryDataSource);
super.setTargetDataSources(targetDataSources);
super.setDefaultTargetDataSource(primaryDataSource); // 设置默认数据源
super.afterPropertiesSet(); // 调用父类初始化
}
}
```
- **说明**:`determineCurrentLookupKey()` 依赖 `DataSourceContextHolder` 提供 key。引用[^4] 展示了类似实现,并强调 `@Primary` 的必要性。
#### 3. **定义数据源上下文**
使用 `ThreadLocal` 存储当前线程的数据源 key,确保线程安全(避免多线程冲突)。
```java
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
// 设置数据源 key
public static void setDataSourceKey(String dataSourceKey) {
CONTEXT_HOLDER.set(dataSourceKey);
}
// 获取数据源 key
public static String getDataSourceKey() {
return CONTEXT_HOLDER.get();
}
// 清除 key(防止内存泄漏)
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}
}
```
#### 4. **实现 AOP 切面切换数据源**
创建切面(Aspect),在方法执行前通过注解动态设置数据源 key。常用自定义注解(如 `@DataSource`)标记需切换数据源的方法。
- 定义注解:
```java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "db1"; // 默认主数据源
}
```
- 实现切面:
```java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(-1) // 确保在事务切面前执行
public class DataSourceAspect {
@Around("@annotation(dataSource)") // 拦截 @DataSource 注解
public Object around(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {
String key = dataSource.value(); // 获取注解指定的 key
try {
DataSourceContextHolder.setDataSourceKey(key); // 设置 key
return joinPoint.proceed(); // 执行目标方法
} finally {
DataSourceContextHolder.clearDataSourceKey(); // 清理 key
}
}
}
```
#### 5. **事务管理优化**
动态数据源切换需配合事务管理:
- 使用 `@Transactional` 注解时,确保事务管理器(如 `DataSourceTransactionManager`)绑定到 `DynamicDataSource`。
- 在配置类中定义事务管理器:
```java
@Bean
public PlatformTransactionManager transactionManager(DynamicDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
```
- **注意事项**:AOP 切面顺序(`@Order(-1)`)必须高于事务切面,否则切换可能失效[^2]。
#### 6. **使用示例**
在 Service 层方法上添加 `@DataSource` 注解切换数据源:
```java
@Service
public class UserService {
@DataSource("db1") // 使用主数据源
public void addUser(User user) {
// 操作数据库
}
@DataSource("db2") // 切换到次数据源
public void queryData() {
// 操作数据库
}
}
```
#### 性能与可靠性说明
- **性能**:基于 ThreadLocal 的切换轻量级,开销小(纳秒级),但需避免频繁切换。
- **可靠性**:此方案已广泛用于生产环境,如多租户 SaaS 系统[^1][^2]。确保测试事务回滚和并发场景。
- **常见问题**:如果切换失败,检查:
1. `DynamicDataSource` 是否添加 `@Primary`。
2. AOP 切面顺序是否正确。
3. 事务管理器是否绑定到动态数据源。
通过以上步骤,您可以实现灵活的数据源切换。引用[^2] 详细描述了此流程的实际应用。
### 相关问题
1. 如何优化动态数据源切换的性能(例如连接池配置)?
2. 在分布式事务中(如 Seata),如何保证动态数据源的一致性?
3. 动态数据源切换有哪些常见陷阱(如线程泄漏)及解决方案?
阅读全文
相关推荐




















