最近开发水文监测系统时遇到了多数据源切换问题:明明存在的表却报 “Table doesn’t exist”。本文记录排查过程和解决方案,希望能帮助遇到类似情况的朋友。
问题背景
大断面是水文监测中的重要概念,用于展示河道/水库的横断面形状。本次任务是为前端图表组件提供适配的数据接口。
系统配置了三个数据源:
| 数据源 |
数据库 |
用途 |
| MASTER |
hydrology |
主业务数据库 |
| SLAVE |
hydrology |
从库(默认关闭) |
| WATER |
ysq |
水务数据库(独立连接) |
问题发现
错误现象
接口测试时直接报错:
1
| Table 'hydrology.st_rvsect_b' doesn't exist
|
这个表明明存在,为什么会报不存在?
问题分析
经过排查发现,ST_RVSECT_B 表实际存储在 WATER 数据源,但代码直接调用了 Mapper 层,绕过了 Service 层的 @DataSource 注解,导致使用了默认的 MASTER 数据源。
错误代码示例
1 2 3 4 5 6 7 8 9
| @Autowired private StRvsectBMapper stRvsectBMapper;
public CrossSectionResponseDTO getCrossSection(String stcd) { List<StRvsectB> sectionPoints = stRvsectBMapper.selectLatestSectionPointsByStcd(stcd); }
|
这种写法的问题在于:多数据源切换是通过 AOP 实现的,只有在 Service 层的方法上添加 @DataSource 注解才能生效。直接调用 Mapper 会使用默认数据源。
解决方案
修复代码
在 Service 层添加方法并配置数据源注解:
1 2 3 4 5 6 7 8 9
| List<StRvsectB> selectLatestSectionPointsByStcd(String stcd);
@Override @DataSource(value = DataSourceType.WATER) public List<StRvsectB> selectLatestSectionPointsByStcd(String stcd) { return stRvsectBMapper.selectLatestSectionPointsByStcd(stcd); }
|
然后在 Biz 层调用 Service:
1 2 3 4 5 6 7 8 9
| @Autowired private IStRvsectBService stRvsectBService;
public CrossSectionResponseDTO getCrossSection(String stcd) { List<StRvsectB> sectionPoints = stRvsectBService.selectLatestSectionPointsByStcd(stcd); }
|
架构示意
1 2 3 4 5 6 7 8 9 10
| Controller/Biz 层 │ ▼ Service 层 ◄── @DataSource 注解在这里生效 │ ▼ Mapper 层 │ ▼ 数据库(根据注解切换)
|
响应格式重构
解决数据源问题后,还需要重构响应格式以适配前端图表组件。
新旧格式对比
旧格式:
1 2 3 4 5
| { "points": [{ "distance": 0, "elevation": 95 }], "warningWaterLevel": 120, "waterHistory": [...] }
|
新格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "information": { "normz": 120, "actz": 69.797, "dsflz": 125, "ckflz": 127, "fsltdz": 119, "flcnhgwl": 124 }, "riverbedDataSource": [ { "x": 0, "y": 95 }, { "x": 10, "y": 92 } ], "dataSource": [{ "z": 120, "q": 100 }], "name": "白垢", "riverbedNames": ["起点距(m)", "高程(m)"] }
|
DTO 设计
使用内部类组织嵌套结构,保持代码整洁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Data public class CrossSectionResponseDTO {
private Information information; private List<RiverbedPoint> riverbedDataSource; private List<FlowPoint> dataSource; private String name; private List<String> riverbedNames;
@Data public static class Information { private BigDecimal normz; private BigDecimal actz; private BigDecimal dsflz; private BigDecimal ckflz; private BigDecimal fsltdz; private BigDecimal flcnhgwl; }
@Data public static class RiverbedPoint { private Double x; private Double y; }
@Data public static class FlowPoint { private BigDecimal z; private BigDecimal q; } }
|
经验总结
多数据源开发的注意事项
- 永远通过 Service 层访问数据库,不要在 Biz/Controller 层直接注入 Mapper
- 数据源注解放在 Service 实现类的方法上,确保 AOP 拦截生效
- 跨数据源查询要考虑事务问题,必要时需要分布式事务方案
业务术语要确认清楚
本次开发中遇到了术语的坑:
| 术语 |
含义 |
单位 |
| 兴利水位 |
水位高度 |
m |
| 兴利库容 (ACTCP) |
水的体积 |
10^6m³ |
数据库中没有”兴利水位”字段,只有”兴利库容”,开发前务必与业务方确认字段含义。
空数据处理
不同类型测站的数据完整性不同,接口应正确处理空数据情况:
| 测站类型 |
断面数据 |
特征水位 |
水位-流量 |
| 水库站 (RR) |
可能无 |
有 |
通常无 |
| 河道站 (ZQ) |
可能有 |
有 |
有 |
结语
多数据源开发中,数据源切换的时机和位置非常重要。记住一个原则:让数据源切换发生在正确的层次。
希望这篇文章对你有帮助!