在同一个 Service 方法中调用多个不同数据源的 Mapper 时,出现**数据源切换失效**的问题:
@Service
public class DepartmentSyncServiceImpl {
@Autowired
private DepartmentSyncMapper departmentSyncMapper; // @DataSource(SQLSERVER)
@Autowired
private SysDeptMapper sysDeptMapper; // 默认 MySQL
@Transactional
public AjaxResult syncBranchDepartments() {
// 第一步:从 SQL Server 查询数据
List<DepartmentSyncDTO> data = departmentSyncMapper.selectBranchDepartments();
// ✅ 这里能正确查询 SQL Server
// 第二步:写入 MySQL
sysDeptMapper.insertDept(dept);
// ❌ 问题:这里也去了 MySQL,因为数据源被清除了
}
}
现象:
- 第一个 Mapper 调用能够正确切换到 SQL Server
- 调用完成后,数据源被清除
- 后续的 MySQL Mapper 调用正常
- 但如果再次调用 SQL Server Mapper,会失败(使用了 MySQL 数据源)
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
if (StringUtils.isNotNull(dataSource)) {
// 设置数据源
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
}
finally {
// ❌ 问题:无论如何都会清除数据源
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
finally 块中都会调用 clearDataSourceType()@Transactional 注解文件路径: ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
// ✅ 关键改进 1: 记录当前数据源
String oldDataSourceType = DynamicDataSourceContextHolder.getDataSourceType();
boolean isNewDataSource = false;
if (StringUtils.isNotNull(dataSource)) {
String newDataSourceType = dataSource.value().name();
// ✅ 关键改进 2: 只有当数据源发生变化时才设置新的数据源
if (!newDataSourceType.equals(oldDataSourceType)) {
DynamicDataSourceContextHolder.setDataSourceType(newDataSourceType);
isNewDataSource = true;
logger.debug("切换数据源: {} -> {}", oldDataSourceType, newDataSourceType);
}
}
try {
return point.proceed();
}
finally {
// ✅ 关键改进 3: 只有当本次调用改变了数据源时,才需要恢复
if (isNewDataSource) {
// 恢复到之前的数据源
if (StringUtils.isNotEmpty(oldDataSourceType)) {
DynamicDataSourceContextHolder.setDataSourceType(oldDataSourceType);
logger.debug("恢复数据源: {}", oldDataSourceType);
} else {
DynamicDataSourceContextHolder.clearDataSourceType();
logger.debug("清除数据源,恢复到默认数据源");
}
}
}
}
String oldDataSourceType = DynamicDataSourceContextHolder.getDataSourceType();
if (!newDataSourceType.equals(oldDataSourceType)) {
DynamicDataSourceContextHolder.setDataSourceType(newDataSourceType);
isNewDataSource = true;
}
isNewDataSource 标记记录是否进行了切换if (isNewDataSource) {
if (StringUtils.isNotEmpty(oldDataSourceType)) {
// 恢复到之前的数据源
DynamicDataSourceContextHolder.setDataSourceType(oldDataSourceType);
} else {
// 之前没有设置数据源,清除当前数据源
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
Service.syncBranchDepartments() 开始
├─> departmentSyncMapper.selectBranchDepartments()
│ ├─ 切换到 SQL Server ✅
│ ├─ 执行查询 ✅
│ └─ 清除数据源 ❌ (finally 块)
│
├─> sysDeptMapper.checkDeptNameUnique()
│ └─ 使用默认数据源 MySQL ✅
│
├─> sysDeptMapper.insertDept()
│ └─ 使用默认数据源 MySQL ✅
│
└─> 如果再次调用 departmentSyncMapper
└─ 使用默认数据源 MySQL ❌ (应该是 SQL Server)
Service.syncBranchDepartments() 开始
├─> departmentSyncMapper.selectBranchDepartments()
│ ├─ 当前数据源: null (默认)
│ ├─ 切换到 SQL Server ✅
│ ├─ 执行查询 ✅
│ └─ 恢复到默认数据源 ✅ (因为之前是 null)
│
├─> sysDeptMapper.checkDeptNameUnique()
│ ├─ 当前数据源: null (默认 MySQL)
│ ├─ 无需切换 ✅
│ └─ 执行查询 ✅
│
├─> departmentSyncMapper.selectBranchDepartments() (再次调用)
│ ├─ 当前数据源: null (默认)
│ ├─ 切换到 SQL Server ✅
│ ├─ 执行查询 ✅
│ └─ 恢复到默认数据源 ✅
│
└─> sysDeptMapper.insertDept()
├─ 当前数据源: null (默认 MySQL)
├─ 无需切换 ✅
└─ 执行插入 ✅
stateDiagram-v2
[*] --> 默认数据源(MySQL)
默认数据源(MySQL) --> SQL_Server: departmentSyncMapper调用
SQL_Server --> 默认数据源(MySQL): 方法执行完成,恢复
默认数据源(MySQL) --> 默认数据源(MySQL): sysDeptMapper调用(无切换)
SQL_Server --> SQL_Server: 嵌套调用SQL_Server_Mapper(无切换)
默认数据源(MySQL) --> [*]: Service方法结束
@Transactional
public void method1() {
// 切换到 SQL Server
List<Data> data = sqlServerMapper.selectData();
// 自动恢复到 MySQL
mysqlMapper.insertData(data);
}
@Transactional
public void method2() {
// 切换到 SQL Server
List<Data1> data1 = sqlServerMapper.selectData1();
// 恢复到 MySQL
mysqlMapper.process(data1);
// 再次切换到 SQL Server
List<Data2> data2 = sqlServerMapper.selectData2();
// 再次恢复到 MySQL
mysqlMapper.saveAll(data2);
}
@Transactional
public void method3() {
// 切换到 SQL Server
List<Dept> depts = sqlServerMapper.selectDepartments();
for (Dept dept : depts) {
// 仍然是 SQL Server (不会重复切换)
List<User> users = sqlServerMapper.selectUsersByDept(dept.getId());
// 恢复到 MySQL
mysqlMapper.saveUsers(users);
}
}
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodA() {
// 默认 MySQL
mysqlMapper.query();
// 调用 ServiceB
serviceB.methodB();
// 仍然是 MySQL
mysqlMapper.insert();
}
}
@Service
public class ServiceB {
public void methodB() {
// 切换到 SQL Server
sqlServerMapper.query();
// 方法结束后恢复到调用者的数据源 (MySQL)
}
}
修改后的切面会输出详细的调试日志,帮助追踪数据源切换:
2025-10-18 10:00:01.123 DEBUG [DataSourceAspect] 切换数据源: null -> SQLSERVER
2025-10-18 10:00:01.456 DEBUG [DynamicDataSourceContextHolder] 切换到SQLSERVER数据源
2025-10-18 10:00:01.789 DEBUG [DataSourceAspect] 清除数据源,恢复到默认数据源
2025-10-18 10:00:02.123 DEBUG [DataSourceAspect] 切换数据源: null -> SQLSERVER
2025-10-18 10:00:02.456 DEBUG [DynamicDataSourceContextHolder] 切换到SQLSERVER数据源
2025-10-18 10:00:02.789 DEBUG [DataSourceAspect] 清除数据源,恢复到默认数据源
在 Service 方法中添加数据源状态日志:
@Transactional
public AjaxResult syncBranchDepartments() {
log.info("开始同步,当前数据源: {}",
DynamicDataSourceContextHolder.getDataSourceType());
// 查询 SQL Server
List<DepartmentSyncDTO> data = departmentSyncMapper.selectBranchDepartments();
log.info("查询 SQL Server 完成,当前数据源: {}",
DynamicDataSourceContextHolder.getDataSourceType());
// 写入 MySQL
sysDeptMapper.insertDept(dept);
log.info("写入 MySQL 完成,当前数据源: {}",
DynamicDataSourceContextHolder.getDataSourceType());
}
@Test
public void testDataSourceSwitch() {
// 测试数据源切换
AjaxResult result = departmentSyncService.syncBranchDepartments();
// 验证结果
assertEquals(200, result.get("code"));
}
# 执行同步接口
POST http://localhost:8080/system/dept/sync/branch
# 查看日志输出
tail -f logs/ruoyi-admin.log | grep -E "数据源|DataSource"
ThreadLocal 存储数据源类型| 文件 | 说明 |
|---|---|
DataSourceAspect.java |
数据源切换切面(已修复) |
DynamicDataSourceContextHolder.java |
数据源上下文持有者 |
DepartmentSyncMapper.java |
部门同步 Mapper(标注 @DataSource) |
UserSyncMapper.java |
用户同步 Mapper(标注 @DataSource) |
DepartmentSyncServiceImpl.java |
部门同步 Service(使用多数据源) |
原始实现无条件清除数据源,导致嵌套调用时数据源状态丢失。
记录旧数据源状态,条件性切换,智能恢复数据源。
✅ 支持同一 Service 中多次切换数据源
✅ 支持嵌套调用
✅ 支持 Service 间调用
✅ 完全兼容原有功能
✅ 代码已修改
⏳ 等待测试验证