当数据在使用 DB2 的 IBMi 上为 'clob' 时,如何从存储过程中获取数据?

how to get the data out of a stored procedure when the data is a 'clob' on an IBMi using DB2?

我正在从 java JVM 访问 IBMi 运行ning DB2。我有一个 return 是 CLOB 的存储过程 我想从 Java.

访问 clob

存储过程的sql是:

CREATE OR REPLACE PROCEDURE ZZSP (
IN fromjson VARCHAR(16000) CCSID 1208,
out tojson clob(10000000) CCSID 1208,
out last_id decimal(20, 0),
out sha VARCHAR(20) CCSID 1208,
out version VARCHAR(20) CCSID 1208)
LANGUAGE RPGLE
SPECIFIC ZZSP
DETERMINISTIC
MODIFIES SQL DATA
CALLED ON NULL INPUT
COMMIT ON RETURN YES
EXTERNAL NAME 'ZZSP'
PARAMETER STYLE SQL;

可以从 IBMi 客户端访问解决方案中的 'run sql scripts' 访问存储过程:

create variable MYCLOB5 clob(10000000) ccsid 1208;
CALL ZZSP ('{"returnStatus":" ","fetchKey":{"inp1":1,"seq1":2},"status":"X"}',myClob5,?,?,?);
values myClob5;

当我这样做时,'values myClob5' 包含一个 json 字符串,它几乎与传入的 json 字符串相同(因为存储过程就是这样做的)。因此,我假设此调用的 rpg 代码(也称为 ZZSP)正在运行。

然而,当我尝试从 java 访问它时,我 运行 遇到了问题。我可以使用此代码从结果集中获取数据,但不能从语句中获取数据。在调用之前,我们已经在可调用语句中设置了参数:

void doit(CallableStatement s){ 
      cs.setString(1, json)
      cs.registerOutParameter(2, Types.CLOB)
      cs.registerOutParameter(3, Types.DECIMAL)
      cs.registerOutParameter(4, Types.VARCHAR)
      cs.registerOutParameter(5, Types.VARCHAR)
 // setup the statement with the sql 'call ZZSP(?,?,?,?,?)', and execute it
   
   val jsonOutClob = s.getClob(2)
   val json = jsonOutClob.getSubString(1, jsonOutClob.length().toInt)
   ...
} 

虽然所有其他数据 return 来自存储过程 (outParameters 3,4,5) return 正确值,但 clob 中的数据是 'garbage':它对我来说看起来不像一个字符串。我期望的数据类似于 '{"returnStatus":" ","fetchKey":{"inp1":1,"seq1":2},"status":"X"}',但实际数据类似于 '?zɕƿk??z?????????????⣁zտЀ'。同样有趣的是,clob 的长度是预期字符串长度的一半。 returned 字符串中的字节以 List(3f, f1, 85, a3, a4, f1, 95, a2, a3, f1, a3, a4, a2, 7f, 7a, 7f, c9, 95,...)

开头

我已经尝试过其他的语句方法:例如getCharacterStream,它return是相同的数据。

感觉是字符编码的问题,不知道怎么解决。它也感觉它可能是我设置为 'sql' 的 'parameter style' 周围的东西。我们也尝试过一般的 'iac' 程序也是 return 的垃圾。

我正处于这个问题的第二周...我们在 RPG 代码中遇到了很多其他问题,这些问题已经得到修复,但是对于这个问题,我感到很紧张。

当数据是 'clob' 时,谁能告诉我如何从存储过程中获取数据?如果那不可能,你能告诉我如何从存储过程中获取一个大字符串(可能是一兆字节)吗?

技术栈:

谢谢!

编辑以回答 Charles 问题:

rpg程序是这样启动的。这会不断更改,因为我们正在试验如何使 toJson 具有正确的 CCSID。两个 /Set ccsid (*char : *UTF8) 显然不是都需要的。我们已经尝试了所有四种排列 (none/first/second/both)。这是我们从 IAC.

调用程序时 'works' 的代码
**free
ctl-opt option(*srcStmt:*noDebugIO) dftactgrp(*no) actgrp('Somegroup') CCSID(*char:*jobRun);

exec SQL
  set option commit = *none;
/Set ccsid (*char : *UTF8)
  dcl-s toJsontemplate        sqltype(CLOB:10000000);
/restore CCSID(*char)

dcl-pi *n;
  fromJson  varChar(16000) ccsid(*utf8);
  /Set ccsid (*char : *UTF8)
  toJson        likeds(toJsontemplate);
  /restore CCSID(*char)
  id        packed(20);
  sha       varChar(20) ccsid(*utf8);
  version   varChar(20) ccsid(*utf8);
end-pi;

设置变量的地方是这样的

  exec SQL
   set :tojson = json_object(
       'returnStatus'            : trim(:returnStatus),
       'fetchKey'                : json_object(...

编辑

按照答案中的建议,我使用 opt-ctl 将 ccsid 设置为全局。

**free
ctl-opt option(*srcStmt:*noDebugIO) dftactgrp(*no) actgrp('Somegroup') CCSID(*char:*UTF8);

exec SQL
  set option commit = *none;
  dcl-s toJsontemplate        sqltype(CLOB:10000000);

dcl-pi *n;
  fromJson  varChar(16000) ccsid(*utf8);
  toJson        likeds(toJsontemplate);
  id        packed(20);
  sha       varChar(20) ccsid(*utf8);
  version   varChar(20) ccsid(*utf8);
end-pi;

这完全没有区别。我认为变量 tojson 包含 utf8 数据(除非 RPG 中存在错误),因为 opt-ctl 为整个模块设置了 ccsid。我认为现在很清楚(因为 IAC 实际上可以正确读取 clob)这是 Jdbc 和存储过程之间的某种协商问题。 IAC 正确地执行并获取了正确的数据,而我的代码没有正确地获取了错误的数据。

知道我可以从这里去哪里吗?

是的,我怀疑你对 CCSID 做错了什么,但具体是什么我不能说。

RPG 程序实际上在做什么?真的需要吗?您可能会发现使用实际的 SQL 存储过程(用 SQL 编写)要容易得多。

如果确实需要RPG程序...

我首先质疑 CCSID 东西的使用。 Db2 for i 和 JDBC 驱动程序在从 EBCDIC CCSID 转换为 Java 使用的 UTF-16 时没有问题。除非您的输入实际上包含仅存在于 Unicode 中的非字母数字字符。

Db2 for i 和 RPG 通常擅长使用 Unicode。但是一些旧的做法经常会引起问题。

  1. 没有为系统设置默认的 CCSID dspsysval qccsid 它应该不是 65535。
  2. 相关,没有在您的表中的字符列上设置 CCSID dspffd MYTABLE
  3. 作业 运行在 CCSID 65535 下,如果系统设置为实际的默认 CCSID,则不应出现这种情况。

假设所有这些都很好,我认为编码

ctl-opt ccsid(*EXACT);
ctl-opt ccsid(*CHAR:*UTF8);

会导致 RPG 代码全面使用 UTF-8 数据。当使用本机 RPG 文件操作码读取时,列为 CCSID(37) 的数据库表将转换为 UTF-8。

这里有一篇关于 CCSID 和 RPG 的好文章presentation

编辑
关于“质疑是否需要 CCSID 东西”的要点是 Db/JDBC 驱动程序可以很好地将 EBCDIC 转换为 Java 使用的 UTF-16。特别是英文 ccsid(37) 和 Unicode 的 ASCII 子集。

现在您正在与 30 多个国家/地区打交道,但据我所知,上述情况适用于大多数 CCSID,但我并没有真正处于该位置。

但是,JSON 应该是 UTF-8、UTF-16、UTF-32,所以是的,“正确”的做法是使用 Unicode 参数。但是你需要一直使用 Unicode 并备份 RPG 调用堆栈,否则你可能 运行 变成不可转换的字符。

我也从未真正研究过 RPG SQL 关于 EBCDIC/Unicode 转换的预编译器功能。

我怀疑你的

  exec SQL
   set :tojson = json_object(
       'returnStatus'            : trim(:returnStatus),
       'fetchKey'                : json_object(...

导致系统认为是 UTF-8 的 tojson 中的 EBCDIC 数据,因此它 returns 原样。

这比预期的要难得多。最后我直接放弃了:我无法按照我预期的方式将clob的结果放入java。

然而,“IT 总有办法”。所以我们最终做了与在 IAC 中工作时相同的事情:

我们使用了全局变量(本例中为 'clob5')。 'some library'、but each session gets it's own value 中定义了全局变量,因此我们没有并行性问题。变量只定义一次就不用管了

当我们调用存储过程中的过程时,我们使用与我们使用的命令相同的命令

CALL ZZSP ('{"returnStatus":" ","fetchKey":{"inp1":1,"seq1":2},"status":"X"}',myClob5,?,?,?);

为了从变量中获取值,我们使用了(使用相同的连接)

SELECT myclob5 from SYSIBM.SYSDUMMY1