Files
iot-dc3/docs/zh/drivers/postgresql.md

157 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: PostgreSQL 驱动
---
# PostgreSQL 驱动
`dc3-driver-postgresql` 把一个 PostgreSQL 数据库当作数据源接入 IoT DC3:按采集周期执行 `SELECT` 把查到的值当采集值,并支持用位号上配置的
`UPDATE`/`INSERT` 写查询向库里写值。读完你能把已有库表里的字段当位号采进平台,并理解读/写两条 SQL 路径各自的边界。
## 协议背景
不是所有数据都来自现场协议设备。很多业务数据、历史数据、第三方系统沉淀的结果,本身就躺在一张 PostgreSQL 表里——MES
的工单状态、计量系统结算后的累计量、上游平台只对外开放的一个数据库视图。这类数据没有 Modbus、OPC UA
这样的现场总线协议可走,但它们确实是设备世界的延伸,需要被纳入统一的位号体系来管理和消费。
PostgreSQL 是一套开源的对象-关系型数据库(ORDBMS),以严格的 SQL 标准遵循度、强事务(MVCC)、丰富的数据类型(JSON/JSONB、数组、范围、几何)著称,默认监听
`5432` 端口,客户端通过标准 JDBC(`org.postgresql.Driver`)连接。把它接入物联网平台,本质上是把"数据库当成一类设备"
:一张表的一行一列,就是一个位号的取值来源。
在物联网四层参考架构里,数据库桥接驱动横跨网络层与平台层的边界——它不解析任何现场总线报文,而是把"已经落到库里的数据"
以位号的形式重新接入采集管线。理解这条路径如何与时序存储、流处理衔接,见[时序数据与流处理](../foundations/data-pipeline)。
::: info 与现场协议驱动的本质差异
Modbus、OPC UA 这类驱动面对的是"物理量在线"——寄存器实时变化、读到的就是此刻的现场值。数据库驱动面对的是"数据已落库"
——你读到的是某个上游系统写入的、可能已经过期的快照。把数据库字段当位号采集时,采集周期与上游写库节奏要对得上,否则会反复采到同一个旧值。
:::
## 属性配置
接入分三类属性:**驱动属性**(设备级,定位到哪个库、用什么账号)、**位号属性**(每个位号的读/写 SQL)、**写命令属性**
(保留项,当前未生效)。所有属性的默认值都来自驱动的 `application.yml`
,在[设备](../introduction/concepts/device)/[位号](../introduction/concepts/point)
上为它们填具体值即可,三层来历见[属性与配置](../introduction/concepts/attribute-config)。
### 驱动属性(设备级 `driver-attribute`)
把一个 PostgreSQL 库接进来时,在[设备](../introduction/concepts/device)
上填这些[属性](../introduction/concepts/attribute-config)。它们决定连到哪个库、用什么账号、连接超时多久:
| 属性 | code | 类型 | 默认值 | 说明 |
|---------------|----------------|--------|-------------|------------------------------------------------|
| Host | `host` | STRING | `localhost` | PostgreSQL 主机地址 |
| Port | `port` | INT | `5432` | PostgreSQL 端口 |
| Database | `database` | STRING | (空) | 要连接的数据库名 |
| Username | `username` | STRING | `root` | 连接账号 |
| Password | `password` | STRING | (空) | 账号密码 |
| Query Timeout | `queryTimeout` | INT | `30` | 连接获取超时(秒),同时作为 Hikari 连接池的 `connectionTimeout` |
驱动用 `host`、`port`、`database` 拼出 JDBC URL(形如 `jdbc:postgresql://host:port/database`)。设备配置校验(`validate()`
)会逐项检查 `host`、`port`、`database`、`username`、`password` 五项是否填写——任一为空都不通过;`queryTimeout` 非必填,缺省按
`30` 秒。每个设备在驱动内对应一个独立的 HikariCP 连接池(`maximumPoolSize=5`、`minimumIdle=1`),按设备 ID 缓存复用。
### 位号属性(`point-attribute`)
每个采集[位号](../introduction/concepts/point)上填它对应的读/写 SQL:
| 属性 | code | 类型 | 默认值 | 说明 |
|-------------|--------------|--------|-----|----------------------------------------------|
| Read Query | `readQuery` | STRING | (空) | 读取位号值的 `SELECT` 语句 |
| Write Query | `writeQuery` | STRING | (空) | 写值用的 `UPDATE`/`INSERT`,用单个 `?` 占位待写入值(按参数绑定) |
::: tip Read Query 取结果的第一行第一列
`readQuery` 是一条普通 `SELECT`,驱动执行后取结果集**第一行的第一列**(`rs.getObject(1)`)作为该位号的值,再 `toString()`
交给位号按其数据类型([Point](../introduction/concepts/point) 的 `pointTypeFlag`)解析。所以写成
`SELECT temperature FROM sensor WHERE id = 1` 这种只返回单行单列的查询最稳妥。`readQuery` 是位号的必填项,位号校验(
`validatePoint()`)缺它不通过。
:::
### 写命令属性(`command-attribute`)
写命令上保留了一个属性,但当前不被实现消费:
| 属性 | code | 类型 | 默认值 | 说明 |
|---------------|----------------|--------|-----|-----------------|
| Execute Query | `executeQuery` | STRING | (空) | 命令执行用的 SQL(保留项) |
::: warning `executeQuery` 当前未被实现消费
写值实际走的是位号上的 `writeQuery`:驱动的 `write()` 取 `point-attribute` 的 `writeQuery`,用预编译参数绑定执行 `UPDATE`/
`INSERT`。`command-attribute` 上的 `executeQuery` 仅作为配置项保留,当前驱动代码中没有任何地方读取或执行它——不存在"
按命令直接执行一段 SQL"的独立路径。需要写值时,请把 SQL 配在位号的 `writeQuery` 上,配 `executeQuery` 不会生效。
:::
### 采集与健康
- **采集周期**:默认 cron `0/30 * * * * ?`(每 30 秒读一轮)。
- **自定义周期**:驱动还有一个 custom 调度,默认 cron `0/5 * * * * ?`(每 5 秒);JDBC 数据库驱动的 `schedule()`
为空实现,该调度当前不做任何事。
- **健康/在线**:设备健康检查默认 cron `0/15 * * * * ?`,租约超时 `45 秒`。健康检查通过从连接池取一条连接、`conn.isValid(5)`
判断库是否可达——连不上则设备置为离线。在线状态机制见[设备](../introduction/concepts/device)。
## 故障排查
数据库桥接接入失败,多半不在协议本身,而在连接参数、SQL 形态或上游库的状态。以下按出现频率排列:
::: warning 设备一直离线 / 连接池建不起来
健康检查靠 `conn.isValid(5)` 判定。若设备始终离线,先确认 `host`/`port` 可达(容器内连宿主机别用 `localhost`)、`database`
名大小写正确、账号 `username`/`password` 有该库的登录权限。PostgreSQL 默认还受 `pg_hba.conf` 约束——来源 IP
或认证方式不被允许时会直接拒连,这类错误不在驱动侧,要去库端放行。
:::
::: warning Read Query 必须只读且能定位到单值
驱动只取 `readQuery` 结果的第一行第一列,所以查询要返回单行单列、且带 `WHERE`
主键条件稳定定位目标行。返回多行/多列时只会取到第一个,可能不是你想要的那条;查询无结果时位号值为 `null`。别在 `readQuery`
里写 `UPDATE`/`DELETE`——采集是只读路径,写进去会污染数据。把过滤条件写全,避免随表数据变化取到错行。
:::
::: warning Write Query 用 `?` 占位符,不是 `${value}`
写值时 `writeQuery` 用单个 `?` 代表写入值(如 `UPDATE sensor SET temperature = ? WHERE id = 1`),驱动用
`ps.setString(1, value)` 把命令参数作为 JDBC 参数绑定——这是预编译参数绑定,不是字符串拼接,天然防 SQL 注入。不要手动拼接值,也不要用
`${value}` 这类模板语法:那样既不会被替换、也失去防注入的好处。写操作以"受影响行数 > 0"判成功,`UPDATE` 命中 0 行(`WHERE`
没匹配到)会被当作写失败。
:::
::: tip 库名 / 表名大小写按 PostgreSQL 规则
PostgreSQL 对不加引号的标识符会折叠成小写,加双引号则按字面大小写匹配。`database` 填错大小写连不上库;`readQuery`/
`writeQuery` 里表名、列名大小写与实际建库时不符也会报"不存在"。按真实大小写填,必要时在 SQL 里用双引号包住标识符。
:::
::: tip 查询超时 / 慢 SQL
`queryTimeout`(默认 30 秒)被用作 Hikari 连接池的 `connectionTimeout`,即"取不到连接时的等待上限"。涉及大表或慢 SQL 时,应优化
SQL、加索引、缩小 `WHERE` 范围,而不是一味调大超时——慢查询会占住连接池(仅 5 条连接),拖累整批位号的采集。
:::
## 在 IoT DC3 中如何落地
- **驱动名 / code**:`PostgreSQL Driver` / `PostgresqlDriver`(`dc3.driver.code` 是稳定路由标识,平台内据它路由消息,不可随意改)。
- **类型**:`DRIVER_CLIENT`——驱动主动连库并发起查询,不监听外部推送。
- **读 / 写 / 订阅能力**:与[驱动能力矩阵](./matrix)一致——读 ✓、写 ✓、订阅 —。读走 `SELECT` 取首值,写走 `writeQuery`
的预编译绑定;数据库没有变更订阅,靠周期轮询拉取。
::: info 实现状态:可用(非骨架)
PostgreSQL 驱动是**可用实现**,不是骨架。连接、读、写、健康检查均由共享的 `dc3-common-sql` 抽象基类
`AbstractJdbcDriverCustomService` 落地(与 MySQL、Oracle、SQL Server 共用同一套逻辑),PostgreSQL 子类只提供 JDBC URL 拼装、驱动类名
`org.postgresql.Driver` 和默认端口 `5432`。唯一需要留意的是 `command-attribute` 的 `executeQuery` 属性保留但未接线(见上文属性配置)。
:::
### 最小接入示例
把一张 `sensor` 表里 `id=1` 那行的 `temperature` 字段当温度位号采进来:
1. 选 `PostgreSQL Driver` 创建[设备](../introduction/concepts/device),driver 属性填 `host=192.168.1.10`、`port=5432`、
`database=iot`、`username=root`、`password=******`。
2. 给设备绑定的[物模型](../introduction/concepts/profile)加一个温度[位号](../introduction/concepts/point)(
`pointTypeFlag=FLOAT`、`READ_ONLY`),point 属性填 `readQuery=SELECT temperature FROM sensor WHERE id = 1`。
3. 启动驱动,30 秒内就能在[位号值](../introduction/concepts/point-value)里看到查到的温度值。
需要可写位号时,再在同一位号的 `writeQuery` 上配 `UPDATE sensor SET temperature = ? WHERE id = 1`,并把位号 `rwFlag`
设为可写。一次完整流程见[设备接入](../operation/device-onboarding)。
## 延伸阅读
- [驱动总览](./index) — 全部驱动的分类与选型地图
- [驱动能力矩阵](./matrix) — 各驱动读/写/订阅能力一览
- [设备接入](../operation/device-onboarding) — 一次完整的接入流程
- [时序数据与流处理](../foundations/data-pipeline) — 采到的位号值如何落到时序库、被流处理消费
- [MySQL 驱动](./mysql) — 另一种 JDBC 数据库数据源,配置结构完全一致