【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 8 篇】

发布时间:2026/10/7 18:34:27

【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 8 篇】 档案放 PostgreSQL时序放 TDengine——这是很多 IoT 系统的标准姿势。但真要把两个数据源塞进同一个 Spring Boot 应用配置、注入、防注入、分页、健康检查每一步都有坑。本文用实战源码讲透双数据源查询层的完整实现。数据写进去了怎么查出来前几篇我们把 MQTT 消息落进了 TDengine 超级表设备档案存进了 PostgreSQL。现在问题来了一个 Spring Boot 应用怎么同时连两个数据库你可能会想搞个分布式事务引入中间件都不需要。查询层的做法是让两个数据源各司其职服务层编排。今天这篇我们就从零拆解java-api/模块的双数据源实现。双数据源配置4 个 Bean 打天下先看核心配置类DataSourceConfiguration.java。这里定义了 4 个 Bean两个 DataSource两个 JdbcTemplate。**档案数据源PG**用 Hikari 连接池标了PrimaryBean(metadataDataSource)PrimaryConfigurationProperties(spring.datasource.hikari)publicHikariDataSourcemetadataDataSource(Qualifier(metadataDataSourceProperties)DataSourcePropertiesproperties){returnproperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();}**时序数据源TDengine**用的是DriverManagerDataSource每次新建连接不给连接池Bean(tdengineDataSource)publicDataSourcetdengineDataSource(TdenginePropertiesproperties){DriverManagerDataSourcedataSourcenewDriverManagerDataSource();dataSource.setDriverClassName(com.taosdata.jdbc.ws.WebSocketDriver);dataSource.setUrl(properties.url());dataSource.setUsername(properties.username());dataSource.setPassword(properties.password());returndataSource;}注意这个驱动com.taosdata.jdbc.ws.WebSocketDriver。它走的是TAOS-WS 协议连的是 6041 端口——正是第 6 篇里那个 WebSocket 门。这意味着 Java 侧不需要装本地客户端一个 JDBC 驱动就搞定。URL 长这样jdbc:TAOS-WS://localhost:6041/iot?userrootpasswordtaosdata两个 JdbcTemplate 也对应建好metadataJdbcTemplate和tdengineJdbcTemplate。前者查档案后者查时序。注入时用Qualifier区分——比如 Repository 里是构造器注入publicTelemetryRepository(Qualifier(tdengineJdbcTemplate)JdbcTemplatejdbc){this.jdbcjdbc;}TDengine 侧没配连接池DriverManagerDataSource每次查询新建连接。查询型接口的负载可控时这样最省事连接数压上去再换 Hikari 也不迟。时序模板额外设了两件事setQueryTimeout(30)慢查询 30 秒掐断和setFetchSize(1000)流式取数防止大结果集撑爆内存。为什么档案和时序必须分开有人问都放一个库里不行吗看数据特征就明白了。PG 里的 device 表id、display_name、product_key、factory_id、workshop_id、device_type、region、enabled、metadata JSONB。这是档案数据——小、低频变更、事务性强。改名、停用、改 metadata都是典型的关系库操作。TDengine 里的 telemetry 超级表温度、湿度、电压、电流……这是时序数据——大、只追加、按时间窗口聚合。用 TDengine 的超级表 窗口函数一条 SQL 就能算小时均值。关键约束不跨库 join。档案在 PG时序在 TDengine一个查询里拿不到两张表。怎么办服务层编排。比如查询设备最新状态先从 PG 查设备档案再从 TDengine 查最新遥测点服务层把两者拼装成DeviceLatest返回查询白名单列名和 INTERVAL 怎么防注入这是本篇的一个关键点。看这段聚合 SQLStringsql SELECT _wstart AS window_start, AVG(%s) AS average_value, ... FROM iot.telemetry WHERE device_id ? AND ts ? AND ts ? INTERVAL(%s) .formatted(safeMetric,safeMetric,safeMetric,safeMetric,safeInterval);注意聚合列名和 INTERVAL 是拼进去的但设备 ID 和时间范围全用?参数绑定。为什么列名不能参数绑定因为 SQL 语法上列名和窗口函数参数不是值?占位符在这里不生效。你没法写AVG(?)让 JDBC 帮你填列名。那怎么办白名单校验。privatestaticfinalSetStringMETRICSSet.of(temperature,humidity,voltage,current_value,power,pressure,flow_rate,rotational_speed,vibration);METRICS 白名单 9 项INTERVALS 白名单 7 项10s / 30s / 1m / 5m / 15m / 1h / 1d请求里的 metric 参数先进requireMetric()校验不在白名单直接抛IllegalArgumentException。校验通过后才用.formatted()拼进 SQL。设备 ID 和时间范围呢全部?绑定。Timestamp.from(start)直接传参JDBC 驱动处理转义。这就是双保险能参数绑定的绝不拼字符串不能绑定的用白名单锁死。limit1 分页多查一条比多查一页便宜分页查询有个经典问题怎么知道还有没有下一页常规做法是再查一次 count。这里用的是limit1ListTelemetryPointrowsrepository.findTelemetry(deviceId,start,end,safeOffset,safeLimit1);returnPageResponse.of(rows,safeOffset,safeLimit);查limit 1条如果返回的行数大于请求的 limit说明还有更多数据booleanhasMorerows.size()requestedLimit;ListTitemshasMore?rows.subList(0,requestedLimit):rows;多查一条比多查一页便宜——省掉一次 count 查询代价只是一条记录的传输。PageResponse四个字段items / offset / limit / hasMore。配套的QueryRangeValidator做参数校验start/end 必填且 start end跨度 ≤ maxRangeDays默认 31 天limit 1~5000offset ≥ 0违规直接抛InvalidQueryException。在线状态5 分钟阈值 HAVING 过滤设备在线怎么判定看最新一条遥测的时间戳。privatestaticfinalDurationONLINE_THRESHOLDDuration.ofMinutes(5);booleanonlinepoint!nullpoint.timestamp().isAfter(Instant.now().minus(ONLINE_THRESHOLD));最新点时间在 now-5min 内算在线否则离线。阈值固定 5 分钟这是业务规则写死在常量里。离线设备列表呢看这条 SQLSELECTdevice_idFROMiot.telemetryGROUPBYdevice_idHAVINGLAST(ts)?LIMIT?注意是 HAVING 不是 WHERE。为什么WHERE 在分组前执行这时候 LAST(ts) 还没算出来。HAVING 在分组后过滤才能用聚合函数的计算结果。/api/operations/offline-devices?minutes30limit这个接口minutes 参数限制 1~4320030 天防止有人传个负数或超大值把 TDengine 压垮。PG 档案 CRUDJSONB 和 RETURNING 的妙用档案侧的几个细节值得说。metadata 是 JSONB 类型插入时用 ObjectMapper 序列化后 CASTINSERTINTOdevice(...)VALUES(?,...,CAST(?ASjsonb))RETURNING*RETURNING 一步拿回更新后的行UPDATEdeviceSET...WHEREid?RETURNING*如果返回 null说明设备不存在抛NotFoundException。省了一次 select。可选过滤用CAST(? AS VARCHAR) IS NULL技巧WHERE(CAST(?ASVARCHAR)ISNULLORfactory_id?)参数为 null 时条件恒真一个 SQL 搞定可选过滤不用动态拼 SQL。异常处理上DuplicateKeyException转成ConflictException返回 409 语义。健康检查与可运维性TDengine 挂了API 不能挂——这是设计原则。TdengineHealthIndicator做探针SELECTSERVER_VERSION()成功即 UP异常 DOWN。Kubernetes 的 liveness/readiness 探针可以直接用这个端点。其他配置server.port 默认 8080JAVA_API_PORT环境变量可覆盖shutdown: graceful30 秒排空compression 开启management 暴露 health/info/prometheus/metricsspringdoc 开启/swagger-ui.html 在线文档接口全景与双库协作边界最后过一遍 8 个 Controller 的接口清单方法路径说明POST /api/devices创建设备metadata 任意 JSONGET /api/devices?factoryIddeviceTypeoffsetlimit分页列表可选过滤GET /api/devices/{id}档案详情PUT /api/devices/{id}更新含 enabled 停用GET /api/devices/{deviceId}/latest最新遥测 online 布尔GET /api/devices/{deviceId}/telemetry?startendoffsetlimit分页遥测GET /api/devices/{deviceId}/aggregate?metricintervalstartend窗口聚合GET /api/factories/{factoryId}/statistics?metricstartend工厂级汇总GET /api/vehicles/{vehicleId}/track?startendoffsetlimit车辆轨迹分页GET /api/operations/offline-devices?minuteslimit离线设备列表注意 alarm-rules 相关接口只列了名那是第 9 篇的素材——告警引擎会展开讲。双库协作的边界不 join、不跨库事务、服务层编排。档案查 PG时序查 TDengine数据在 Service 层拼装。回顾一下双数据源查询层的核心就这么几件事两个 DataSource 两个 JdbcTemplatePrimary Qualifier 区分白名单防注入能参数绑定的全绑定不能绑定的锁白名单limit1 分页多查一条判断 hasMore在线判定最新点 5 分钟阈值离线用 HAVING 聚合后过滤健康检查TDengine 挂了 API 照样跑你在实际项目里遇到过双数据源的坑吗比如连接池耗尽、事务失效、或者 SQL 注入的隐患欢迎在评论区聊聊你的方案。觉得有用点个关注持续获取优质内容。
延伸阅读

更多相关文章

2026/10/6 18:23:29

领克01全域安全理念解析:从被动防御到主动守护的汽车安全进化

1. 从“被动防御”到“主动守护”:领克01安全理念的底层逻辑 聊到汽车安全,很多人的第一反应还是“这车钢板厚不厚”、“有几个气囊”、“碰撞测试几颗星”。这没错,但这是最基础、最被动的安全。就像我们以前买防盗门,只看钢板厚…

2026/10/6 18:26:22

汽车核心零部件更换周期全解析:告别盲开,科学养车指南

1. 从“坏了再换”到“按时更换”:一个被忽视的养车逻辑 很多车主朋友,包括我自己刚开车那会儿,都信奉一个朴素的养车原则: “没坏就不用换” 。听起来很省钱,很实在,对吧?直到我的车在高速上…

2026/10/7 18:31:51

LTspice电压源设置全攻略:从直流到正弦波、脉冲与PWL一次讲透

LTspice电压源设置,这算是所有仿真里最基础也最绕不开的一关。不管你后面是做电源、放大器、滤波器,还是给单片机ADC前端做信号调理,只要你在LTspice里画原理图,第一件事基本都是放一个电压源。我见过不少刚上手的朋友卡在这里&am…

2026/10/7 18:31:51

工业协议语义网关:破解多协议混用难题

1. 为什么工业现场的协议问题从来不是“技术不行”,而是“系统失能”你见过这样的机房吗?——一台PLC用Modbus RTU,隔壁温控柜跑的是BACnet MS/TP,空调机组固件只认LonWorks,而新上的能耗监测平台却要求HTTPJSON&#…

2026/10/7 18:31:51

Spring AI + React Agent 阿里云生产落地实战

1. “降SpringAI阿里第9掌-或跃在渊-ReactAgent”不是玄学口诀,而是工程落地的临界点 你搜“SpringAI 阿里 ReactAgent”,刷出来的全是零散关键词:springai项目、阿里云RDS、宝塔面板改OSS密钥失败、阿里v2滑块、阿里云AI Agent白皮书……没人…

2026/10/7 18:31:51

Auto Quality Chooser:VM脚本实现多产品混线参数自动切换

做机器视觉项目的工程师,应该都有过这种经历:产线明明是同一条,今天跑A产品、明天换B产品,换个料号就得登录调试电脑,把曝光、增益、对比度、像素格式从头到尾手动改一遍。白天调完晚上光照一变,还得再来一…

2026/10/7 18:26:51

Space Bunny匿名模型服务:黑盒API的工程化实践与接入指南

1. 这不是又一个“免费大模型”——Space Bunny 登顶背后的匿名模型真相最近刷技术社区,几乎每天都能看到“Space Bunny 登顶全球调用量第一”这类标题。它不是某个新发布的开源模型,也不是某家大厂的旗舰产品,而是一个在 OpenRouter 平台上悄…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/7 8:18:33

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

ESP32免重刷固件:浏览器直接修改NVS键值实现WiFi配置更新

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

SAP HANA查询结果导出CSV:避开乱码、性能与权限的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

数字后端Placement阶段Density与Congestion控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑