Skip to content

openPlant Java 接口说明

1. 概述

1.1 项目简介

openPlant Java APIcom.magus:openplant-java-api)是 openPlant 工业实时/历史数据库的 Java 客户端 SDK。它通过 JNI 调用 Rust 编写的原生动态库(opapi_rs4j),使用 OPIO 二进制协议与 openPlant 数据库服务器通信,提供完整的数据访问能力。详细接口说明请见Demo示例。
该项目的 Maven 坐标:

xml
<groupId>com.magus</groupId>
<artifactId>openPlant-java-api</artifactId>
<version>1.0.45.3</version>

1.2 架构分层

┌─────────────────────────────────────┐
│       用户应用层 (你的代码)           │
├─────────────────────────────────────┤
│   JDBC 驱动 (jdbc:openplant://)     │
│   IOPConnect API + OPConnect        │
├─────────────────────────────────────┤
│   请求/响应层 (OPRequest/OPResponse) │
├─────────────────────────────────────┤
│   JNI 层 (OPServiceJNI)             │
├─────────────────────────────────────┤
│   原生动态库 (opapi_rs4j)            │
│   (Windows/Linux/macOS)             │
├─────────────────────────────────────┤
│   OpenPlant 数据库服务器              │
└─────────────────────────────────────┘

1.3 支持的数据类型

OPValue 类定义了所有支持的数据类型:

类型常量说明
TYPE_NULL0空值
TYPE_BOOL1布尔值
TYPE_INT82字节
TYPE_INT163短整型
TYPE_INT324整型
TYPE_INT645长整型
TYPE_FLOAT6浮点型
TYPE_DOUBLE7双精度
TYPE_DATETIME8日期时间
TYPE_STRING9字符串
TYPE_BINARY10二进制
TYPE_OBJECT11自动类型推断
TYPE_ARRAY12数组类型
TYPE_MAP13Map 类型
TYPE_ROW32行类型
TYPE_BOOL_ARRAY17布尔数组
TYPE_INT32_ARRAY20整型数组
TYPE_INT64_ARRAY21长整型数组
TYPE_DOUBLE_ARRAY23双精度数组
TYPE_STRING_ARRAY25字符串数组

1.4 测点类型

OPPointType 类定义了测点类型:

常量说明
TYPE_AX0浮点数类型
TYPE_DX1开关量类型
TYPE_I22短整型
TYPE_I43整型
TYPE_R84双精度类型
TYPE_LONG5长整型
TYPE_TEXT6文本类型
TYPE_BLOB7二进制类型

2. 环境搭建与依赖

2.1 Maven 依赖

xml
<dependency>
    <groupId>com.magus</groupId>
    <artifactId>openplant-java-api</artifactId>
    <version>1.0.45.3</version>
</dependency>

2.2 原生库

SDK 通过 JNI 加载原生动态库,已内置以下平台的动态库:

平台文件
Windows x64opapi_rs4j-x64.dll
Windows x86opapi_rs4j-x86.dll
Linux x64opapi_rs4j-lin-x64.so
Linux ARM64opapi_rs4j-lin-aarch64.so
macOS Intelopapi_rs4j-mac-x64.dylib
macOS ARMopapi_rs4j-mac-aarch64.dylib

注意:需要确保操作系统架构与原生库匹配,JNILoader 会自动根据系统加载对应的动态库。

2.3 Java 版本要求

  • Java 8+(项目编译 target 为 1.8)

3. 系统数据表

openPlant 数据库定义了 8 张系统表:

表名常量用途主要字段
DatabaseTABLE_DATABASE数据库信息ID, GN, ED
NodeTABLE_NODE节点(设备/区域)定义ID, GN, ED, ND
PointTABLE_POINT测点定义ID, GN, ED, RT, AV, DS, TM, EU, HH, HL, ...
RealtimeTABLE_REALTIME实时数据ID, GN, AV, DS, TM
ArchiveTABLE_ARCHIVE历史数据ID, GN, AV, DS, TM
AlarmTABLE_ALARM实时报警ID, GN, AV, DS, TM, TA, SP, ...
AAlarmTABLE_AALARM历史报警ID, GN, AV, DS, TM, TA, SP, ...
StatTABLE_STAT统计数据ID, GN, MAXV, MAXTIME, MINV, MINTIME, AVGV, ...

字段说明

字段说明
ID唯一数字标识(int)
GN测点全名(字符串,如 W3.NODE1.TEST1
UD唯一十进制标识(long)
AV实时值/模拟值
DS数据状态(位掩码)
TM时间戳
ED描述信息
RT测点类型
ND节点ID
TA报警类型
SP报警设定值
MAXV最大值
MINV最小值
AVGV平均值

测点名(GN)命名规范

测点名采用点分格式:数据库名.节点名.测点名

例如:W3.NODE1.TEST1 表示在 W3 数据库下 NODE1 节点中的 TEST1 测点。

4. 连接管理

4.1 直接连接

java
// 创建连接
IOPConnect conn = new OPConnect(host, port, timeout, user, password);

// 带压缩模式的连接 (0=不压缩, 1=frame压缩, 2=block压缩)
IOPConnect conn = new OPConnect(host, port, timeout, user, password, OPConst.MODEL_FRAME);

// TLS加密连接
IOPConnect conn = new OPConnect(host, port, timeout, user, password, model, true);

// 使用连接
OPResultSet rs = conn.execSQL("select * from Database limit 1");

// 关闭连接
conn.close();

参数说明

  • host:数据库服务器 IP
  • port:数据库服务器端口
  • timeout:连接超时时间(秒)
  • user:数据库用户名
  • password:数据库密码
  • model:压缩模式,0=不压缩,1=frame压缩,2=block压缩

4.2 连接池管理

使用 OPConnectManage 连接池(基于 Apache Commons Pool2)管理连接,推荐生产环境使用。

第一步:创建连接配置

java
OPConf config = new OPConf(ip, port, user, password, useCompress, timeout, useTLS);

第二步:初始化连接池

java
// 参数:配置, 最大连接数, 最小空闲连接数
OPConnectManage manage = OPConnectManage.getInstance(config, 30, 2);

第三步:获取和释放连接

java
IOPConnect conn = manage.getConnect();      // 获取连接
// ... 执行业务操作 ...
manage.freeConnect(conn);                    // 释放连接回池

完整示例

java
public class OPPool {
    private final static String IP = "61.169.126.131";
    private final static int PORT = 38300;
    private final static String USER = "sis";
    private final static String PWD = "openplant";
    private final static int MAX = 30;
    private final static int MIN = 2;
    private final static int TIMEOUT = 120;
    private static OPConnectManage manage;

    static {
        OPConf config = new OPConf(IP, PORT, USER, PWD, true, TIMEOUT, true);
        manage = OPConnectManage.getInstance(config, MAX, MIN);
    }

    public static IOPConnect getConn() {
        return manage.getConnect();
    }

    public static void freeConn(IOPConnect conn) {
        manage.freeConnect(conn);
    }
}

4.3 连接状态检测

java
conn.isValid();     // 检查连接是否有效(handle > 0)
conn.isActive();    // 检查连接是否活跃(自动心跳检测,每5分钟ping一次)
conn.pingPong();    // 手动心跳检测
conn.status();      // 连接状态:0=正常,非0=异常
conn.getServerTime(); // 获取数据库服务器时间
conn.getVersion();  // 获取版本信息
conn.getServerInfo(); // 获取服务器信息
conn.getCompress(); // 获取当前压缩模式
conn.setCompress(OPConst.MODEL_FRAME); // 设置压缩模式

5. 核心接口 IOPConnect

IOPConnect 是 API 的核心接口,定义了所有与 openPlant 数据库交互的方法。

接口总览

方法分类说明
execSQL(String sql)SQL执行执行 SQL 语句
execSQL(String sql, Object keys)SQL执行带索引键的 SQL 执行
execSQLHander(SQLBindHandler)SQL执行防 SQL 注入的参数化查询
find(OPTable, Object keys)数据查询按索引查询数据和测点定义
like(OPTable, String, String)模糊查询模糊匹配查询
notLike(OPTable, String, String)模糊查询反向模糊匹配
getQueryWrapper(OPTable)链式查询创建链式过滤条件包装器
findByQueryWrapper(QueryWrapper)链式查询执行链式过滤查询
findArchive(...)历史查询查询历史数据/历史报警/统计
findArchiveTmAsc(...)历史查询按时间正序查询历史
insert(OPTable)数据写入插入数据
update(OPTable)数据更新更新数据
replace(OPTable)数据写入替换数据
remove(String, Object keys)数据删除按索引删除
removeArchive(Object, Date, Date)数据删除删除历史数据
alarmACK(...)报警操作报警确认
alarmInhibit(...)报警操作报警抑制
padlock(...)控制操作挂牌/解锁
forceOn(...)控制操作手工强制设定值
forceOff(...)控制操作取消强制
control(...)控制操作提交控制指令
controlFeedback(...)控制操作控制反馈
getCompress()连接管理获取压缩模式
setCompress(int)连接管理设置压缩模式
reset()连接管理重置连接状态
createTable(String)工具创建表模型对象
close()连接管理关闭连接
destroy()连接管理销毁连接

6. 数据模型

6.1 OPTable(表模型)

OPTable 是数据操作的核心模型类,代表一张数据库表。

创建表模型

java
OPTable table = conn.createTable("Point");           // 通过连接创建
// 或
OPTable table = new OPTable("Point");                // 直接创建

定义列

java
// 批量添加列(类型为 TYPE_OBJECT,自动推断)
table.addColumns("ID", "GN", "ED", "AV", "DS", "TM");

// 单个添加列(指定类型)
table.addColumn("GN", OPValue.TYPE_STRING);
table.addColumn("AV", OPValue.TYPE_DOUBLE);
table.addColumn("TM", OPValue.TYPE_DATETIME);
table.addColumn("DS", OPValue.TYPE_INT16);

添加数据行

java
OPRow row = table.newRow();
row.bind(0, "W3.NODE1.TEST1");          // GN
row.bind(1, 75.5);                       // AV
row.bind(2, System.currentTimeMillis() / 1000.0, OPValue.TYPE_DATETIME); // TM
row.bind(3, (short)0);                  // DS
table.append(row);                       // 绑定到表

遍历结果集

java
while (table.next()) {
    String gn = table.getString(0);      // 按列序获取字符串
    double av = table.getDouble(1);      // 按列序获取双精度
    long tm = table.getLong(2);          // 按列序获取长整型
    Date date = table.getDate(2);        // 按列序获取日期
    int id = table.getInt(3);            // 按列序获取整型
    boolean bool = table.getBoolean(4);  // 按列序获取布尔
    byte[] bytes = table.getBytes(5);    // 按列序获取二进制
    OPValue val = table.getOPValue(6);   // 按列序获取OPValue
}

获取列元数据

java
List<OPColumn> columns = table.getColumns();

资源释放

java
table.destroy();  // 或 table.close();

6.2 OPRow(行模型)

OPRow 代表表中的一行记录,用于数据写入。

java
OPRow row = table.newRow();

// 自动类型推断绑定
row.bind(0, "W3.NODE1.TEST1");          // 自动推断为字符串
row.bind(1, 75.5);                       // 自动推断为双精度
row.bind(2, (short)0);                   // 自动推断为短整型

// 指定类型绑定
row.bind(0, "W3.NODE1.TEST1", OPValue.TYPE_STRING);
row.bind(1, 75.5, OPValue.TYPE_DOUBLE);
row.bind(2, System.currentTimeMillis() / 1000.0, OPValue.TYPE_DATETIME);

// 直接绑定OPValue
OPValue value = new OPValue(75.5);
row.bind(1, value);

6.3 OPValue(值封装)

OPValue 用于封装带类型的值,支持类型安全转换。

java
// 创建
OPValue v1 = new OPValue(75.5);         // 自动推断为 DOUBLE
OPValue v2 = new OPValue("hello");       // 自动推断为 STRING
OPValue v3 = new OPValue(true);          // 自动推断为 BOOL

// 类型转换
double d = v1.getDouble();              // 作为double获取
String s = v1.getString();              // 作为string获取
long l = v1.getLong();                  // 作为long获取
byte[] b = v1.getBytes();               // 作为byte[]获取
Object o = v1.getObject();              // 获取原始对象

6.4 OPColumn(列元数据)

java
OPColumn col = columns.get(0);
String name = col.getColumnName();      // 列名
int type = col.getColumnType();         // 列类型
int index = col.getColumnIndex();       // 列索引

7. 基本数据操作(CRUD)

7.1 查询数据(find)

按索引键(GN/ID/UD)查询测点、节点、实时数据等。

java
IOPConnect conn = OPPool.getConn();

// 创建表模型并声明要查询的字段
OPTable table = conn.createTable(OPConst.TABLE_POINT);
table.addColumns("ID", "GN", "ED", "RT");

// 按GN查询(字符串数组)
String[] points = {"W3.NODE1.TEST1"};
OPResultSet result = conn.find(table, points);

// 按ID查询(int数组)
int[] ids = {1001, 1002};
OPResultSet result = conn.find(table, ids);

// 按UD查询(long数组)
long[] uds = {1000001L};
OPResultSet result = conn.find(table, uds);

// 解析结果
while (result.next()) {
    int id = result.getInt("ID");
    String gn = result.getString("GN");
    String ed = result.getString("ED");
    System.out.println(id + "\t" + gn + "\t" + ed);
}
result.close();
table.destroy();
OPPool.freeConn(conn);

7.2 插入数据(insert)

插入测点 Point

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_POINT);
table.addColumns("GN", "ED", "RT");

OPRow row = table.newRow();
row.bind(0, "W3.NODE1.TEST1");              // GN
row.bind(1, "测试测点1");                    // ED 描述
row.bind(2, OPPointType.TYPE_AX);           // RT 测点类型
table.append(row);

OPResultSet result = conn.insert(table);
// EC为0表示成功
result.close();
table.destroy();
OPPool.freeConn(conn);

插入实时数据 Realtime

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_REALTIME);
table.addColumns("GN", "AV", "DS", "TM");

OPRow row = table.newRow();
row.bind(0, "W3.NODE1.TEST1");
row.bind(1, Math.random() * 100);           // AV 实时值
row.bind(2, 0);                        // DS 状态位,0表示GOOD
row.bind(3, System.currentTimeMillis() / 1000.0, OPValue.TYPE_DATETIME); // TM 时间戳
table.append(row);

OPResultSet result = conn.insert(table);
result.close();
table.destroy();
OPPool.freeConn(conn);

插入历史数据 Archive

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_ARCHIVE);
table.addColumn("GN", OPValue.TYPE_STRING);
table.addColumn("AV", OPValue.TYPE_DOUBLE);
table.addColumn("TM", OPValue.TYPE_DATETIME);
table.addColumn("DS", OPValue.TYPE_INT16);

// 历史数据必须按时间顺序或倒序插入
for (int i = 0; i < 10; i++) {
    OPRow row = table.newRow();
    row.bind(0, "W3.NODE1.TEST1");
    row.bind(1, 77.0 + i);
    row.bind(2, System.currentTimeMillis() / 1000.0 - 10 * i, OPValue.TYPE_DATETIME);
    row.bind(3, (short)0);
    table.append(row);
}

OPResultSet result = conn.insert(table);
result.close();
table.destroy();
OPPool.freeConn(conn);

注意:历史数据插入必须按时间顺序或者倒序,否则会插入失败。

7.3 更新数据(update)

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_POINT);
table.addColumns("GN", "ED");  // GN作为唯一标识

OPRow row = table.newRow();
row.bind(0, "W3.NODE1.TEST1");   // 指定要更新的测点
row.bind(1, "新的测点描述");      // 更新描述
table.append(row);

OPResultSet result = conn.update(table);
result.close();
table.destroy();
OPPool.freeConn(conn);

7.4 删除数据(remove)

删除测点/节点

java
IOPConnect conn = OPPool.getConn();

// 按GN删除
String[] points = {"W3.NODE1.TEST1"};
OPResultSet result = conn.remove(OPConst.TABLE_POINT, points);

// 按ID删除
int[] ids = {1001};
OPResultSet result = conn.remove(OPConst.TABLE_POINT, ids);

result.close();
OPPool.freeConn(conn);

删除历史数据

java
IOPConnect conn = OPPool.getConn();
String[] points = {"W3.NODE1.TEST1"};
Date end = new Date();
Date start = new Date(end.getTime() - 3600000);

OPResultSet result = conn.removeArchive(points, start, end);
result.close();
OPPool.freeConn(conn);

8. 历史数据查询

8.1 查询模式

OPConst.ArchiveModel 枚举定义了多种历史数据查询模式:

模式说明
raw原始值(需加开始/结束时间)
arch归档原始值
span等间距取值
plot绘图值(优化显示)
flow流量累计值
max区间最大值
min区间最小值
avg面积平均值
mean算术平均值
stdev标准差
sum区间求和
prev区间前第一个值
next区间后第一个值
both前后各一条记录
stat统计查询(用于Stat表)
last区间最后一条
first区间第一条

8.2 查询历史数据

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_ARCHIVE);
table.addColumns("GN", "AV", "TM", "DS");

String[] points = {"W3.NODE1.TEST1"};
Date end = new Date();
Date start = new Date(end.getTime() - 3600000);  // 前一小时

// 查询原始值
OPResultSet result = conn.findArchive(
    table, points, start, end,
    OPConst.ArchiveModel.raw, 1  // 原始值模式,interval不生效
);

// 等间距查询(每60秒取一个值)
OPResultSet result = conn.findArchive(
    table, points, start, end,
    OPConst.ArchiveModel.span, 60  // interval=60秒
);

// 查询最大值
OPResultSet result = conn.findArchive(
    table, points, start, end,
    OPConst.ArchiveModel.max, 300  // 每300秒取一个最大值
);

while (result.next()) {
    String gn = result.getString("GN");
    double av = result.getDouble("AV");
    String tm = result.getString("TM");
    System.out.println(gn + "\t" + av + "\t" + tm);
}
result.close();
table.destroy();
OPPool.freeConn(conn);

8.3 字符串形式的时间参数

java
// 使用字符串格式时间 "yyyy-MM-dd HH:mm:ss.SSS"
OPResultSet result = conn.findArchive(
    table, points,
    "2023-01-01 00:00:00.000",      // begin
    "2023-01-02 00:00:00.000",      // end
    "raw",                           // mode
    "60s"                            // interval
);

8.4 按时间正序查询

java
OPResultSet result = conn.findArchiveTmAsc(
    table, points, start, end,
    OPConst.ArchiveModel.raw, 1
);

9. 报警管理

9.1 查询实时报警(Alarm表)

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_ALARM);
table.addColumns("ID", "GN", "AV", "TM", "DS", "TA");

String[] points = {"W3.NODE1.TEST1"};
OPResultSet result = conn.find(table, points);
// 或使用SQL
// OPResultSet result = conn.execSQL("select * from Alarm where GN = 'W3.NODE1.TEST1'");

printResult(result);
result.close();
table.destroy();
OPPool.freeConn(conn);

9.2 查询历史报警(AAlarm表)

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_AALARM);
table.addColumns("ID", "GN", "AV", "TM", "DS");

String[] points = {"W3.NODE1.TEST1"};
Date end = new Date();
Date start = new Date(end.getTime() - 3600000);

// 历史报警使用findArchive接口,mode和interval参数不生效
OPResultSet result = conn.findArchive(
    table, points, start, end,
    OPConst.ArchiveModel.raw, 1
);
result.close();
table.destroy();
OPPool.freeConn(conn);

9.3 报警确认(alarmACK)

java
// 按ID确认
conn.alarmACK(1001);

// 按GN确认
conn.alarmACK("W3.NODE1.TEST1");

// 批量确认
conn.alarmACK(new int[]{1001, 1002, 1003});
conn.alarmACK(new String[]{"W3.NODE1.TEST1", "W3.NODE1.TEST2"});

9.4 报警抑制(alarmInhibit)

java
// 打开报警抑制
conn.alarmInhibit(1001, true);
conn.alarmInhibit("W3.NODE1.TEST1", true);

// 关闭报警抑制
conn.alarmInhibit(1001, false);
conn.alarmInhibit("W3.NODE1.TEST1", false);

// 批量操作
conn.alarmInhibit(new int[]{1001, 1002}, true);
conn.alarmInhibit(new String[]{"W3.NODE1.TEST1", "W3.NODE1.TEST2"}, true);

10. 控制操作

10.1 挂牌(padlock)

挂牌锁定后测点不能被操作,用于设备检修等场景。

java
// 锁定(挂牌)
conn.padlock(1001, true);                    // 按ID
conn.padlock("W3.NODE1.TEST1", true);       // 按GN
conn.padlock(new int[]{1001, 1002}, true);  // 批量

// 解锁(摘牌)
conn.padlock(1001, false);

10.2 手工强制(forceOn / forceOff)

强制后测点数值不能修改,可重复强制设定值。

java
// 强制设定值
conn.forceOn(1001, 80.5);                    // 按ID
conn.forceOn("W3.NODE1.TEST1", 80.5);       // 按GN

// 解除强制
conn.forceOff(1001);
conn.forceOff("W3.NODE1.TEST1");

10.3 控制指令(control)

提交控制指令,下发给 IO 驱动执行。

java
conn.control(1001, 50.0);                    // 按ID
conn.control("W3.NODE1.TEST1", 50.0);       // 按GN
conn.control(new int[]{1001, 1002}, 50.0);  // 批量

10.4 控制反馈(controlFeedback)

采集端领取指令并执行控制后,通过控制反馈将结果返回。

java
conn.controlFeedback(1001, 50.0);                    // 按ID
conn.controlFeedback("W3.NODE1.TEST1", 50.0);       // 按GN
conn.controlFeedback(new int[]{1001, 1002}, 50.0);  // 批量

11. 订阅机制

订阅功能允许应用实时接收数据库的推送数据,无需轮询查询。

11.1 实时数据订阅

java
IOPConnect conn = OPPool.getConn();

// 创建要订阅的测点列表
ArrayList<String> points = new ArrayList<>();
points.add("W3.SYS.LOAD");
points.add("W3.SYS.DBLOAD");

// 创建订阅(不带nodelay参数,默认缓冲推送)
OPSubscribe subscribe = new OPSubscribe(
    conn,
    OPConst.TABLE_REALTIME,     // 订阅实时数据
    points,                     // 测点列表
    resultSet -> {
        // 回调处理推送的数据
        OPResultSet rs = (OPResultSet) resultSet;
        try {
            while (rs.next()) {
                System.out.println(rs.getString("GN") + "\t" + rs.getDouble("AV"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        rs.close();
    }
);

// 带 isNodelay 参数
OPSubscribe subscribe = new OPSubscribe(
    conn, OPConst.TABLE_REALTIME, points,
    resultSet -> { /* 回调处理 */ },
    false                       // isNodelay: false=缓冲推送, true=实时推送
);

// 带 options 参数(可配置 NoDelay/Snapshot 等选项)
HashMap<String, String> options = new HashMap<>();
options.put("NoDelay", "0");   // 0=缓冲推送, 1=实时推送
options.put("Snapshot", "1");  // 1=先推送当前快照

OPSubscribe subscribe = new OPSubscribe(
    conn, OPConst.TABLE_REALTIME, points, options,
    resultSet -> { /* 回调处理 */ }
);

// 取消订阅指定测点
ArrayList<String> unsubList = new ArrayList<>();
unsubList.add("W3.SYS.LOAD");
subscribe.unsubscribe(unsubList);

// 动态增加订阅测点
subscribe.subscription(newList);

// 关闭订阅
subscribe.close();
OPPool.freeConn(conn);

11.2 报警订阅

java
IOPConnect conn = OPPool.getConn();
ArrayList<String> points = new ArrayList<>();
points.add("W3.SYS.LOAD");

OPSubscribe subscribe = new OPSubscribe(
    conn,
    OPConst.TABLE_ALARM,        // 订阅报警表
    points,
    resultSet -> {
        OPResultSet rs = (OPResultSet) resultSet;
        // 解析推送数据
        rs.close();
    }
);

OPPool.freeConn(conn);

11.3 订阅控制

java
// 动态增加订阅测点
subscribe.subscription(newList);

// 动态取消订阅
subscribe.unsubscribe(removeList);

// 关闭订阅
subscribe.close();

注意

  • 订阅时测点列表可为空(全库订阅),也可传节点名(订阅整个节点)
  • 订阅使用 ID 时,传 ArrayList<Integer>ArrayList<Long>
  • 回调中处理数据后需及时关闭 ResultSet,避免内存泄漏

11.4 控制订阅(OPSubscribeControl)

OPSubscribeControl 用于订阅控制指令,采集端通过此机制接收下发的控制指令并执行控制反馈。

java
IOPConnect conn = new OPConnect("192.168.2.63", 8200, 3600, "sis", "openplant");

ArrayList<String> points = new ArrayList<>();
points.add("W3.TEST");

// 创建控制订阅
OPSubscribeControl subscribeControl = new OPSubscribeControl(
    conn,
    points,
    new SubscribeControlCallBack() {
        @Override
        public boolean onResponse(
                List<LinkedHashMap<String, Object>> needFeedBack,
                List<LinkedHashMap<String, Object>> errList) {
            // needFeedBack: 需要执行控制反馈的记录(DS第12位=1)
            // errList: 不需要反馈的记录
            System.out.println("feedList:" + needFeedBack);
            System.out.println("errList:" + errList);
            // 返回 true 表示 SDK 自动对 needFeedBack 执行 controlFeedback
            // 返回 false 表示由用户自行处理反馈
            return true;
        }
    }
);

// 取消订阅
subscribeControl.unsubscribe(removeList);

// 动态增加订阅
subscribeControl.subscription(newList);

// 关闭订阅
subscribeControl.close();

控制订阅机制说明

  1. 创建 OPSubscribeControl 时,SDK 会建立一个独立的订阅连接
  2. 当有控制指令下发时,回调 onResponse 方法,传入两个列表:
    • needFeedBack:DS 字段第 12 位(0x1000)为 1 的记录,需要执行控制反馈
    • errList:不需要反馈的记录
  3. 回调返回 true 时,SDK 会自动创建独立连接对 needFeedBack 中的每条记录执行 controlFeedback
  4. 回调返回 false 时,由用户自行处理反馈逻辑

12. 模糊查询与链式过滤

12.1 模糊查询 like / notLike

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_POINT);
table.addColumns("ID", "GN", "AV", "ED", "RT");

// like 查询(GN以W3.SYS开头)
OPResultSet result = conn.like(table, "GN", "W3.SYS%");

// notLike 查询(GN不以W3.SYS开头)
OPResultSet result = conn.notLike(table, "GN", "W3.SYS%");

while (result.next()) {
    System.out.println(result.getString("GN"));
}
result.close();
table.destroy();
OPPool.freeConn(conn);

12.2 链式过滤查询(QueryWrapper)

QueryWrapper 提供链式编程风格的复杂过滤条件查询:

java
IOPConnect conn = OPPool.getConn();
OPTable table = conn.createTable(OPConst.TABLE_POINT);
table.addColumns("ID", "GN", "AV", "ED", "RT");

OPResultSet result = conn.getQueryWrapper(table)
    .like("GN", "W3.SYS%")     // GN以W3.SYS开头
    .and()                      // 并且
    .ge("ID", 841513)          // ID >= 841513
    .and()                      // 并且
    .lt("ID", 841519)          // ID < 841519
    .execute();                 // 执行查询

while (result.next()) {
    System.out.println(result.getInt("ID") + "\t" + result.getString("GN"));
}
result.close();
table.destroy();
OPPool.freeConn(conn);

支持的过滤操作

方法说明示例
eq(field, value)等于.eq("GN", "W3.SYS.LOAD")
nq(field, value)不等于.nq("RT", 0)
gt(field, value)大于.gt("ID", 1000)
lt(field, value)小于.lt("ID", 2000)
ge(field, value)大于等于.ge("ID", 1000)
le(field, value)小于等于.le("ID", 2000)
in(field, set)在范围内.in("GN", set)
notIn(field, set)不在范围内.notIn("GN", set)
like(field, value)模糊匹配.like("GN", "W3.%")
notLike(field, value)反向模糊.notLike("GN", "SYS.%")
regex(field, value)正则匹配.regex("GN", "W3\\..*")
and()逻辑与.and()
or()逻辑或.or()
orderBy(value)排序.orderBy("ID desc")
limit(value)限制条数.limit("100")

完整链式示例

java
OPResultSet result = conn.getQueryWrapper(table)
    .like("GN", "W3.%")
    .and()
    .eq("RT", OPPointType.TYPE_AX)
    .or()
    .eq("RT", OPPointType.TYPE_DX)
    .and()
    .ge("ID", 100)
    .le("ID", 1000)
    .orderBy("GN asc")
    .limit("500")
    .execute();

13. SQL 高级查询

13.1 直接执行 SQL

java
IOPConnect conn = OPPool.getConn();

// 简单查询
String sql = "select ID, GN, ED from Point where GN = 'W3.NODE1.TEST1'";
OPResultSet result = conn.execSQL(sql);

// 带索引键的SQL
String sql = "select * from Realtime where ID in ?";
int[] ids = {1001, 1002, 1003};
OPResultSet result = conn.execSQL(sql, ids);

while (result.next()) {
    System.out.println(result.getInt("ID") + "\t" + result.getString("GN"));
}
result.close();
OPPool.freeConn(conn);

13.2 参数化查询(防SQL注入)

使用 SQLBindHandler 实现参数化查询:

java
IOPConnect conn = OPPool.getConn();

String sql = "select ID, GN, AV, DS, TM from Archive where GN = ? and TM between ? and ? and mode = ?";
SQLBindHandler handler = new SQLBindHandler(sql);

Date end = new Date();
Date start = new Date(end.getTime() - 3600000);

// 按?占位符顺序传值
handler.setString(0, "W3.NODE1.TEST1");
handler.setDate(1, start);
handler.setDate(2, end);
handler.setString(3, "raw");

OPResultSet result = conn.execSQLHander(handler);

while (result.next()) {
    System.out.println(result.getString("GN") + "\t" + result.getDouble("AV"));
}
result.close();
OPPool.freeConn(conn);

13.3 统计表SQL查询

java
String sql = "select ID, GN, MAXV, MAXTIME, MINV, MINTIME, AVGV from Stat where GN = ? and TM between ? and ?";
SQLBindHandler handler = new SQLBindHandler(sql);
handler.setString(0, "W3.SYS.LOAD");
handler.setDate(1, start);
handler.setDate(2, end);
OPResultSet result = conn.execSQLHander(handler);

13.4 SQL 支持的语法

openPlant SQL 支持标准 SQL 的常用子集,包括但不限于:

  • SELECT 查询
  • INSERT 插入
  • UPDATE 更新
  • DELETE 删除
  • WHERE 条件过滤
  • AND / OR 逻辑运算
  • IN / NOT IN 范围匹配
  • LIKE / NOT LIKE 模糊匹配
  • BETWEEN AND 区间匹配
  • ORDER BY 排序
  • LIMIT 限制行数
  • mode 历史查询模式(如 mode = 'raw'
  • 表名支持:Point, Node, Realtime, Archive, Alarm, AAlarm, Stat, Database

14. JDBC 驱动使用

openPlant 提供了 JDBC 驱动,允许使用标准 JDBC API 操作数据库。

14.1 注册驱动

java
Class.forName("com.magus.jdbc.Driver");

14.2 连接 URL

jdbc:openplant://host:port/database?user=xxx&password=xxx

14.3 使用标准 JDBC API

java
Connection conn = DriverManager.getConnection(
    "jdbc:openplant://61.169.126.131:38300/sis?user=sis&password=openplant"
);

Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select ID, GN, AV from Realtime limit 10");

while (rs.next()) {
    int id = rs.getInt("ID");
    String gn = rs.getString("GN");
    double av = rs.getDouble("AV");
    System.out.println(id + "\t" + gn + "\t" + av);
}

rs.close();
stmt.close();
conn.close();

14.4 数据库元数据

java
DatabaseMetaData metaData = conn.getMetaData();
// 获取所有表
ResultSet tables = metaData.getTables(null, null, "%", new String[]{"TABLE"});
while (tables.next()) {
    System.out.println(tables.getString("TABLE_NAME"));
}

15. 错误码参考

15.1 OPService 错误码

常量说明
ERROR_OK0正常
ERROR_ERROR-1未知错误
ERROR_TIMEOUT-2超时
ERROR_EOF-3流已关闭
ERROR_MEMORY-96内存不足
ERROR_NET_IO-97网络I/O错误
ERROR_NET_CLOSED-98网络已关闭
ERROR_NET_CONNECT-99无法连接
ERROR_COMMAND-101命令不支持
ERROR_EXIST-102资源已存在
ERROR_NEXIST-103资源不存在
ERROR_DUP_KEY-104关键字重复
ERROR_READ-105读错误
ERROR_WRITE-108写错误
ERROR_LIMIT-106容量限制
ERROR_FOREIGN_KEY-107引用错误
ERROR_ACCESS-109权限不足
ERROR_BUSY-111系统忙
ERROR_INVALID_TYPE-112类型不匹配
ERROR_PARAM-113参数错误
ERROR_OUTDATED-114数据过时
ERROR_LOCKED-115资源被锁定
ERROR_NOT_INIT-116未初始化
ERROR_PARTIAL-117部分错误

15.2 命令类型常量

常量说明
CMD_ACK1报警确认
CMD_INHIBIT2报警抑制
CMD_PADLOCK3挂牌
CMD_FORCE_ON4手工强制
CMD_FORCE_OFF5取消强制
CMD_CONTROL6控制命令
CMD_FEEDBACK7控制反馈

15.3 状态位(DS)掩码

常量说明
ALARM_UNACK0x20报警未确认
ALARM_OFF0x40报警已切除
PADLOCK0x2000挂牌操作位
FORCE0x0100强制输入

15.4 响应格式

所有 API 调用返回 OPResultSet,结果中的 EC 字段表示错误码:

  • EC = 0:操作成功
  • EC != 0:操作失败,可查看错误信息

16. 最佳实践与注意事项

16.1 资源管理

务必正确释放资源,建议使用 try-with-resources 或 finally 块:

java
IOPConnect conn = null;
OPResultSet rs = null;
OPTable table = null;
try {
    conn = OPPool.getConn();
    table = conn.createTable(OPConst.TABLE_POINT);
    table.addColumns("ID", "GN");
    rs = conn.find(table, new String[]{"W3.NODE1.TEST1"});
    // 处理结果...
} finally {
    if (rs != null) rs.close();
    if (table != null) table.destroy();
    if (conn != null) OPPool.freeConn(conn);
}

16.2 连接管理

  • 使用连接池:生产环境务必使用 OPConnectManage 连接池,不要每次调用都创建新连接
  • 连接复用:获取连接后尽快使用并及时归还
  • 空闲超时:连接池配置了 72 小时的空闲回收策略
  • 心跳检测isActive() 方法每 5 分钟自动执行一次 ping

16.3 历史数据写入

  • 历史数据必须按 时间顺序倒序 插入,否则会失败
  • TM 字段使用 System.currentTimeMillis() / 1000.0 转换为秒级时间戳

16.4 性能建议

  • 批量操作:写入多条数据时,在同一个 OPTable 中追加多行后一次性调用 insert()
  • 查询字段:只查询需要的字段,避免 select *
  • 分页查询:使用 LIMIT 限制返回行数
  • 索引查询:优先使用 ID/GN 等索引字段查询
  • 订阅替代轮询:实时数据变化频繁时,使用订阅机制替代定时轮询

16.5 注意事项

  • 测点的 RT(类型)在创建后 不可修改
  • 测点名(GN)中的特殊字符 [] 需注意转义
  • 并发操作时注意 OPConnect 的线程安全问题(部分方法有 synchronized 修饰)
  • 网闸设备环境下,错误码为 100 时没有结果集返回
  • OPConnect 在网络异常时会自动尝试重连

16.6 常见问题

Q: 连接失败怎么办?

  • 检查网络连通性
  • 确认 IP、端口、用户名、密码是否正确
  • 查看服务器是否支持 TLS 加密

Q: 写入历史数据失败?

  • 确认数据是按时间顺序或倒序写入
  • 检查时间戳格式是否正确(秒级时间戳)

Q: 订阅没有收到数据?

  • 检查测点名是否正确
  • 确认 NoDelay 参数设置
  • 确保程序没有退出(订阅是异步的)

Q: 查询超时?

  • 减小查询时间范围
  • 使用索引字段(ID/GN)作为查询条件
  • 适当增加连接超时时间