马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PowerBuilder 用 UPDATEBLOB 与 SELECTBLOB 存取图片等大字段
阅读说明
1. 适用版本:PowerBuilder 9 / 10 / 11 / 12.5(UPDATEBLOB / SELECTBLOB 是嵌入式 SQL 的标准语法,自 PB 6 起即有,所有版本通用;本文代码已在 PB 12.5 实机验证)
2. 支持数据库:SQLite / SQL Server / Oracle / MySQL 等支持二进制大对象(BLOB / IMAGE / VARBINARY(MAX))的数据库;本文示例以 SQLite 实机验证,Blob 语句一字不变适用于其它库
3. 操作系统与环境要求:Windows 7+;演示用 SQLite 需 PBIDEA 提供的 LIT SQLite 接口(PbIdea.dll + sqlite3.dll),标准项目换成你的 SQL Server / Oracle 只需改 SQLCA 连接串,Blob 语句不变
4. 难度系数:★★★☆☆
5. 其它阅读说明:示例用一张图片或任意二进制文件演示;流程是「文件 → Blob → 入库」,取回时是「库 → Blob → 文件」;先把文件读进 Blob 再操作最稳妥
为什么要专门学这两个语句
PowerBuilder 里,普通的 INSERT / SELECT 处理的是「字符串」和「数值」。但图片、PDF、Word 文档、压缩包这类二进制大对象(Binary Large Object,简称 Blob),有两个特点让普通 SQL 处理不了:
- 体积大:一个图片可能几 MB,远超过 string 类型(以及嵌入 SQL 字符串绑定)的上限,塞不进 :ls_text 这种字符串变量。
- 含特殊字节:二进制里有 0x00 等控制字节,当成字符串处理会被截断、乱码。
所以 PB 在嵌入式 SQL 里单独给了两条语句:
- UPDATEBLOB:把内存里的 blob 变量写进某张表的 Blob 列。
- SELECTBLOB:把表里 Blob 列的内容读进内存的 blob 变量。
它们和普通的 UPDATE / SELECT 完全平行,区别只在「操作的是 Blob 列、绑定的是 blob 类型变量」。
一、第一步:把文件读进 Blob(FileOpen + FileReadEx)
存库之前,先把磁盘上的文件读进一个 blob 变量。PB 用 FileOpen 以流模式(StreamMode!)打开,再用 FileReadEx 一次性读入。
前置:窗口上放一个按钮 cb_load,在其 clicked 事件里贴下方代码;先准备一个二进制文件(图片、PDF 都行),记下它的路径。
步骤:1) 打开窗口;2) 点击 cb_load;3) 在 MessageBox 里看到读入的字节数。 - // 示例输入:要读入的文件路径(换成你自己的文件)
- string ls_path
- ls_path = 'C:\temp\photo.png'
- // 接收文件内容的 blob 变量
- blob lb_pic
- int li_f
- long ll_len
- li_f = FileOpen(ls_path, StreamMode!, Read!, LockRead!)
- if li_f < 0 then
- MessageBox('错误', '文件打开失败,请检查路径')
- return
- end if
- ll_len = FileReadEx(li_f, lb_pic)
- FileClose(li_f)
- MessageBox('已读入字节数', string(ll_len))
复制代码
要点:
- StreamMode! 是「二进制流」模式,按字节读,不会像文本模式那样做换行转换——读二进制文件必须用这个模式。
- FileReadEx(fileno, blobvar) 返回实际读到的字节数(读失败返回 -1)。它一次把整个文件读进 blob,比老式的 FileRead 按行读要省事得多。
- 读到的 lb_pic 现在在内存里,可以拿去存库了。
二、写入数据库:UPDATEBLOB
Blob 变量准备好后,用 UPDATEBLOB 写进表的 Blob 列。注意它必须单独成句,不能用普通 UPDATE 的 SET col = :lb_pic 写法(普通 UPDATE 的绑定变量只认字符串/数值)。
前置:已 CONNECT USING SQLCA; 成功;数据表已建好,例如 files(id integer, name varchar(50), content blob),并且已经有一行 id = 1。 - // 示例输入:要写入的 blob(假设已由上一步读入)
- blob lb_pic
- // 示例输入:目标记录 id
- long ll_id
- ll_id = 1
- UPDATEBLOB files
- SET content = :lb_pic
- WHERE id = :ll_id
- USING SQLCA;
- if SQLCA.SQLCode <> 0 then
- MessageBox('写入失败', SQLCA.SQLErrText)
- else
- MessageBox('写入成功', '字节数=' + string(len(lb_pic)))
- end if
复制代码
要点:
- SET content = :lb_pic 右边必须是 :blob变量,冒号绑定。
- WHERE 条件可以带普通变量(如 :ll_id),和普通 UPDATE 一样。
- 必须跟 USING SQLCA;(或你自己的事务对象)。
- 写完一定要检查 SQLCA.SQLCode:0 成功,非 0 看 SQLCA.SQLErrText。
三、取回数据库:SELECTBLOB
取回时方向相反,用 SELECTBLOB ... INTO :blob变量。
- // 示例输入:要取回的记录 id
- long ll_id
- ll_id = 1
- // 接收读出的 blob
- blob lb_back
- SELECTBLOB content
- INTO :lb_back
- FROM files
- WHERE id = :ll_id
- USING SQLCA;
- if SQLCA.SQLCode <> 0 then
- MessageBox('读取失败', SQLCA.SQLErrText)
- else
- MessageBox('取回字节数', string(len(lb_back)))
- end if
复制代码
要点:
- INTO :lb_back 的变量必须是 blob 类型。
- 一次 SELECTBLOB 只能取一个 Blob 列,且 INTO 后面只能跟这一个 blob 变量(不能像普通 SELECT 那样 INTO :a, :b 混着来)。
- 同样必须 USING SQLCA; 并检查 SQLCA.SQLCode。
四、把 Blob 写回文件:FileWriteEx
取回的 blob 要还原成文件,用 FileOpen(流模式写)+ FileWriteEx。
- // 示例输入:写出的目标路径
- string ls_out
- ls_out = 'C:\temp\photo_copy.png'
- // 示例输入:要写出的 blob(已由上一步取回)
- blob lb_back
- int li_o
- li_o = FileOpen(ls_out, StreamMode!, Write!, LockWrite!)
- if li_o < 0 then
- MessageBox('错误', '无法创建目标文件')
- return
- end if
- FileWriteEx(li_o, lb_back)
- FileClose(li_o)
- MessageBox('已写出字节数', string(len(lb_back)))
复制代码
五、完整可运行示例(一个函数串起来)
下面把「读文件 → 入库 → 取回 → 写回验证」完整串在一次调用里,也是本文在 PB 12.5 实机跑通的代码。它以 SQLite 做演示库(LIT SQLite 是 PBIDEA 提供的 SQLite 接口),换成 SQL Server / Oracle 只改 SQLCA 的连接串,UPDATEBLOB / SELECTBLOB 语句一字不变。
- // 示例输入:源二进制文件路径(换成你自己的文件)
- string ls_in
- ls_in = 'C:\temp\sample.bin'
- // 示例输入:写回验证用的目标路径
- string ls_out
- ls_out = 'C:\temp\sample_out.bin'
- int li_f
- long ll_len
- blob lb_data
- // 1) 文件 -> Blob
- li_f = FileOpen(ls_in, StreamMode!, Read!, LockRead!)
- if li_f < 0 then
- MessageBox('错误', '源文件打开失败')
- return
- end if
- ll_len = FileReadEx(li_f, lb_data)
- FileClose(li_f)
- // 2) 连接 SQLite 演示库(生产环境换成你的 MSSQL/Oracle 连接串)
- SQLCA.DBMS = 'LIT SQLite'
- SQLCA.Database = 'pblit_demo.db'
- SQLCA.AutoCommit = true
- CONNECT USING SQLCA;
- if SQLCA.SQLCode <> 0 then
- MessageBox('连接失败', SQLCA.SQLErrText)
- return
- end if
- // 3) 建表并清空旧数据,保证可重复运行
- string ls_sql
- ls_sql = 'create table if not exists files(id integer primary key, name varchar(50), content blob)'
- EXECUTE IMMEDIATE :ls_sql USING SQLCA;
- ls_sql = 'delete from files where id = 1'
- EXECUTE IMMEDIATE :ls_sql USING SQLCA;
- INSERT INTO files(id, name) VALUES(1, 'sample.bin') USING SQLCA;
- // 4) UPDATEBLOB 写入
- UPDATEBLOB files SET content = :lb_data WHERE id = 1 USING SQLCA;
- if SQLCA.SQLCode <> 0 then
- MessageBox('入库失败', SQLCA.SQLErrText)
- DISCONNECT USING SQLCA;
- return
- end if
- // 5) SELECTBLOB 取回
- blob lb_back
- SELECTBLOB content INTO :lb_back FROM files WHERE id = 1 USING SQLCA;
- if SQLCA.SQLCode <> 0 then
- MessageBox('取回失败', SQLCA.SQLErrText)
- DISCONNECT USING SQLCA;
- return
- end if
- DISCONNECT USING SQLCA;
- // 6) 写回文件做往返验证
- int li_o
- li_o = FileOpen(ls_out, StreamMode!, Write!, LockWrite!)
- FileWriteEx(li_o, lb_back)
- FileClose(li_o)
- // 结果输出:三次字节数应一致,证明往返无损
- MessageBox('Blob 往返验证', '读入=' + string(ll_len) + ' 入库=' + string(len(lb_data)) + ' 取回=' + string(len(lb_back)))
复制代码连接串差异(仅举例,Blob 语句不变):SQL Server 用 SQLCA.DBMS = 'SNC SQL Native Client' + SQLCA.ServerName / SQLCA.Database / SQLCA.LogId / SQLCA.LogPass;Oracle 用 SQLCA.DBMS = 'O73 Oracle7' 系列或 O10/O11 驱动。无论哪种,上面的 UPDATEBLOB / SELECTBLOB 四行原样照搬即可。
六、边界情况与常见坑
- 普通 UPDATE 不能改 Blob 列:UPDATE files SET content = :lb_pic 会编译报错或运行失败。必须用 UPDATEBLOB。
- 普通 SELECT INTO 不能取 Blob:SELECT content INTO :ls_x 把 blob 当字符串会截断/报错。必须用 SELECTBLOB。
- SELECTBLOB 一次只能一个 Blob 列:想同时取 Blob 和普通列,分开两条语句,或先把普通列用普通 SELECT 取,Blob 单独 SELECTBLOB。
- 必须检查 SQLCA.SQLCode:Blob 操作出错(列不存在、事务没连、类型不匹配)时 PB 不会抛异常,只会把 SQLCode 置非 0。养成「写完必查」的习惯。
- WHERE 条件不能省且要能定位到行:UPDATEBLOB / SELECTBLOB 都靠 WHERE 锁定行,条件错会把别的行写坏或取空。
- Blob 变量要先有值再写:UPDATEBLOB 把一个空 blob(长度 0)写进去,库里就是空的——不是报错,是「存了个空」。读文件失败时 lb_data 可能为空,先确认 ll_len > 0。
- 事务与提交:如果 SQLCA.AutoCommit = false,UPDATEBLOB 后要 COMMIT USING SQLCA; 才真正落库;AutoCommit = true 则自动提交。忘了提交,取回时可能读不到刚写的内容。
- FileOpen 模式要配对:读用 Read!,写用 Write!;二进制一律 StreamMode!,文本才用 LineMode!。用错模式文件会损坏。
七、与 DataWindow 的 Blob 列对比
如果你的数据已经在 DataWindow 里(比如 d_files 有一列 content 是 blob 类型),不必走嵌入式 SQL,直接用 DataWindow 的方法:
- // 示例输入:行号
- long ll_row
- ll_row = 1
- // 示例输入:blob 列名
- string ls_col
- ls_col = 'content'
- // 从 DataWindow 取 blob 列
- blob lb_col
- lb_col = dw_1.GetItemBlob(ll_row, ls_col)
- // 写回 blob 列
- dw_1.SetItemBlob(ll_row, ls_col, lb_col)
- // 真正落库仍需 Update()
- dw_1.Update()
- MessageBox('DataWindow Blob', '取回字节=' + string(len(lb_col)))
复制代码
两种路线怎么选:
- 嵌入式 UPDATEBLOB / SELECTBLOB:适合后台服务、脚本、不在界面上的批量处理,不依赖 DataWindow。
- DataWindow 的 GetItemBlob / SetItemBlob + Update():适合界面上直接编辑、随 DataWindow 一起提交,和界面绑定更自然。
八、进阶:PBIDEA 的 uo_blob 封装
PBIDEA 的 websuite.pbl 里有一套 uo_blob 对象,对上述「读文件 / 写文件 / 入库 / 取回」做了更顺手的封装(例如把文件直读直写、Base64 编解码、与 uo_database 联动等收进对象方法),适合不想手写 FileOpen/FileReadEx 细节的场景。它的方法签名以本机 PBIDEA/export_20260811/websuite.pbl/uo_blob.sru 为准,需要时对照源码取用即可。本文核心是标准嵌入式 SQL,掌握后接入 uo_blob 只是少写几行胶水代码。
九、小结
- 二进制大字段不能用普通 INSERT / SELECT,必须用 UPDATEBLOB(写)和 SELECTBLOB(读)。
- 标准流程:文件 → FileOpen/FileReadEx 读进 blob → UPDATEBLOB 入库 → SELECTBLOB 取回 → FileWriteEx 写回文件。
- 三条铁律:绑定变量用 :blob、必须 USING SQLCA;、写完必查 SQLCA.SQLCode。
- 界面场景可用 DataWindow 的 GetItemBlob / SetItemBlob 替代,后台/脚本场景用嵌入式 SQL 更直接。
- 本文全部示例已在 PB 12.5 实机跑通(含 SQLite 上的 UPDATEBLOB / SELECTBLOB 往返验证)。
|