DC AI Bot · 五月 26 转到文章

从现有资料里,能确认的点主要有这些:

  1. 在 Java 里调用存储过程,推荐使用 CallableStatement,并按 call 包名.存储过程名(?) 这种形式准备语句,再逐个设置参数后执行;文档示例也是这样做的,而不是直接把参数值硬编码进 SQL 字符串里执行。[1]

  2. InterSystems 文档明确提到,调用存储过程时,语句必须为 SQL 所要求的每一个 input 或 input/output 参数都提供参数。如果参数个数或类型理解不一致,就可能出现你描述的这类 “Parameter list mismatch” 问题。[2]

  3. 另外,InterSystems 也明确提到,JDBC 相关问题里有一类就是:“Caché 对 JDBC 的理解”和“远端数据库/驱动对 JDBC 的理解”之间存在不匹配,其中就包括 stored procedure with output parameters。这种场景官方建议通过日志分析实际发送内容,并用独立 Java 程序复现来定位。[3]

基于这些资料,你现在可以这样检查:

  • 不要只在 Java 中直接执行

    CALL DHCXXXXice.MKPIQuery_QueryXXXX('K007XXXX.JJR','2024-08-01','2024-08-01')

    而应改成使用 CallableStatement,例如先准备类似

    call DHCXXXXice.MKPIQuery_QueryXXXX(?,?,?,?,?,?,?,?)

    这样的语句,再逐个绑定参数后执行。[1][2]

  • 既然你查到该存储过程“可接收参数为 8 个”,那 Java 侧需要确认:这 8 个参数里哪些是 input,哪些是 output / input-output。因为文档特别指出,存储过程调用时需要为每个必需的 input 或 input/output 参数提供参数;而涉及 output 参数时,JDBC 与数据库之间更容易出现不匹配问题。[2][3]

  • 如果连接工具能查出来、Java 代码不行,资料建议的定位方式是:

    1. 启用 JDBC 驱动日志,方法是在 JDBC 连接串末尾追加日志文件名,例如

      jdbc:Cache://127.0.0.1:1972/USER/jdbc.log,日志会写到 Java 应用工作目录。[3]
    2. 写一个最小化 Java 复现程序,只保留连接、prepareCall、参数绑定、执行这几个步骤,用来确认究竟是参数绑定方式、过程签名,还是驱动/数据库对过程参数的解释存在差异。[3]

3 中还有一句和你这个场景非常相关:

[!NOTE]

This reference lists only the extension methods and variants discussed elsewhere in this document. See "JDBC Driver Support" in the Implementation Reference for Java Third Party APIs for a complete description of all InterSystems JDBC driver features, including extensions, variants, and implementation of optional JDBC features. [4]

以及 11 中的关键限制:

[!IMPORTANT]

The name of the stored procedure is case-sensitive. Also, make sure that the pQueryStatement statement supplies an argument for every input or input/output parameter that the SQL query requires. [2]

所以,单从资料能得出的结论是:你的问题更像是 Java/JDBC 对该存储过程参数签名的调用方式不正确,或者该过程包含 output / input-output 参数,导致 JDBC 调用时参数列表解释与连接工具不同[3][2]

如果你愿意,我可以下一步帮你把这个调用改写成一个标准的 Java CallableStatement 示例模板,方便你直接对照代码排查。[1]


Sources:

DC AI Bot · 五月 26 转到文章

你这个现象更像是 Studio 里的 project 没有自动重新打开,而不是项目本身丢了。

文档里说明,project 是一组类定义和例程的集合,作用之一就是让你下次启动 Studio 时更容易回到之前的工作;启动后,打开该 project,Workspace 窗口里的 Project 标签页才会显示这些内容。[1]

如果你关闭了 workspace / project 之后,再打开看不到 project,文档给出的相关操作是:

  • File > Open Project... 打开当前 IRIS 服务器和命名空间中的已有 project。[2]
  • 或者用 File > Recent Projects... 打开最近使用过的 project。[2]
  • 如果只是把当前 project 关掉了,可以用 File > Close Project / Open Project... 重新打开。[2]

还有一个容易混淆的点:project 只属于当前 server 和 namespace。文档明确说:

  • project 只能包含当前 InterSystems IRIS namespace 中可见的内容。[1]
  • Open Project... 也是打开“当前 InterSystems IRIS server 和 namespace”里的 project;如果是别的 namespace 或别的 server,需要先 File > Change NamespaceFile > New Studio[2]

所以你现在“只能从最近的 workspace/project 里找回来”,很可能是因为当前会话里 没有自动打开 project,或者你重新进入时 server / namespace 和之前不一样,于是当前 workspace 的 Project 标签页就空了,需要手动重新打开对应的 project。[1][2]

另外,文档还提到:

[!IMPORTANT]

InterSystems® Studio has been deprecated. Beginning with version 2024.2, it is no longer included with installations of InterSystems IRIS®, and it is no longer receiving maintenance updates. An upgrade to InterSystems IRIS 2024.2 or later will remove any Studio installation which is associated with the instance. [1]

如果你愿意,我可以继续帮你把这个情况拆成“到底是 Close Workspace/Close Project 导致的”,还是“切换了 namespace 导致 project 看不到”的排查步骤。[1][2]


Sources:

DC AI Bot · 五月 25 转到文章

不需要、也不建议在 WaitForComplete()(或 Sync())之后立刻手工去杀 %SYSTEM.WorkMgr 创建的子进程。EVTW 状态通常表示这些 worker 进程还在线,等待后续的 Work Queue 请求;这样做是为了避免下次再启动新进程的开销。它们应当会在一段时间后自动超时清理,因此一般可以直接放着不管。[1]

你看到“过一会儿它们会消失,留一个它不会消失”,从已有资料看,这与 Work Queue Manager 的设计一致:系统会保留 worker 进程以便复用,下次有新的工作项时可以直接接上,而不是每次重新创建进程。[1]

如果你用 $SYSTEM.Process.Terminate(pid) 去结束这些 worker,产生错误信息并不奇怪。文档说明这个方法对其他进程的效果等同于 ^RESJOB,也就是强制终止进程;这不是 %SYSTEM.WorkMgr 推荐的正常结束方式。[2]

关于“为什么会导致服务器出问题”,现有资料并没有支持“这些处于 EVTW 的空闲 worker 本身会消耗明显 CPU 或内存并直接导致服务器卡顿”的结论。相反,社区回复明确说这些 worker “不应消耗显著的内存或 CPU”,并且会自动超时,所以通常应当让它们自行清理。[1]

更正确的使用方式是:

  1. 创建 work queue。
  2. Queue()/QueueCallback() 加入工作项。
  3. 调用 Sync()(或你提到的 WaitForComplete())等待完成。

    这是文档给出的标准流程。[3]

另外,要避免 WorkMgr 使用过程中出现异常或残留影响,关键不在于“快速杀掉子进程”,而在于保证你的工作单元代码满足 Work Queue 的要求,尤其是:

  • 工作单元彼此独立;[3]
  • 返回 %Status,让 Sync() 能汇总总体结果;[3]
  • 如果多个工作单元会修改同一 global,需要自己设计锁策略;[3]
  • 代码里不要使用 exclusive NEWKILLunLOCK,因为这会干扰框架;[3]
  • 工作单元调用的任何逻辑都必须正确清理,不要留下变量、锁、进程私有全局或其他残留,因为同一个 worker 进程会被后续完全不同的工作项复用。[3]

如果你的实际问题是“多次调用后越来越多,想让每次都马上退出”,当前检索结果里没有提供 %SYSTEM.WorkMgr 的公开“立即销毁全部 worker”方法,因此不能据此给出这种做法。[1][3]

[!NOTE]

The %SYSTEM.WorkMgr class supports more complex workflows with the methods described in other topics. [3]

如果你怀疑服务器异常不是“空闲 EVTW 进程”本身造成的,更应优先检查你的工作单元是否留下了锁、变量或其他运行痕迹,因为同一进程会被后续工作项重复使用;这正是文档特别强调要清理干净的原因。[3]

补充一点:如果根本不调用 Sync() / WaitForComplete(),已有讨论指出,work queue 对象一旦离开作用域,可能导致该 work queue 组中的工作单元被终止,因此通常还是需要调用它们作为同步点,并接收错误报告。[4]


Sources:

DC AI Bot · 五月 25 转到文章

You cannot convert an arbitrary XML string directly into a %XML.Adaptor object without a class definition that represents the XML structure. The available material shows that %XML.Reader works together with %XML.Adaptor, but you must first define a class that extends %XML.Adaptor and matches the XML structure, then correlate the XML root or element to that class and read it. [1][2]

A typical pattern is:

Class MyApp.Messages.PatientXML Extends (%Persistent, %XML.Adaptor)
{
Parameter XMLNAME = "Patient";

Property PatientID As %String;
Property PatientName As %String;
Property Age As %String;
Property DateOfBirth As %String;
Property Sex As %String;
Property PatientClass As %String;
Property AssignedPatientLocation As %String;
Property AttendingDoctor As %String;

ClassMethod XMLToObject(xmlStream As %Stream.Object = "", xmlString, filename = "C:\learn\hl7msg\test.xml")
{
    Set reader = ##class(%XML.Reader).%New() 

    If filename'="" {
        Set sc=reader.OpenFile(filename)
    }
    ElseIf $IsObject(xmlStream){
        Set sc=reader.OpenStream(xmlStream)
    }
    ElseIf xmlString'="" {
        Set sc=reader.OpenString(xmlString)
    }
    Else {
        Return $$$ERROR("No file name,string or stream found")
    }

    If $$$ISERR(sc) Do $system.OBJ.DisplayError(sc) Quit

    Do reader.CorrelateRoot($classname())
    Do reader.Next(.patient,.sc) 
    If $$$ISERR(sc) Do $system.OBJ.DisplayError(sc) Quit

    ZWrite patient
}
}

[1]

The more general import flow is:

  1. Create a %XML.Reader instance.
  2. Open the XML from a file, stream, or string.
  3. Associate an XML element with a class by using Correlate() or CorrelateRoot().
  4. Call Next() to instantiate and populate the object. [1][2]

Example:

Set reader = ##class(%XML.Reader).%New()
Set sc = reader.OpenString(xml)
Do reader.Correlate("resultado","EsquemasDatos.Radiologia.Resultado")
Do reader.Next(.ReturnObject,.sc)

This returns an object of the correlated class. [3][4]

If your goal is specifically to handle any XML dynamically without creating a predefined class, the suitable option shown is EnsLib.EDI.XML.Document, not %XML.Adaptor. It allows runtime and XPath-based access to XML content without requiring a predefined schema or class. [1]

Example:

ClassMethod ParseXML(xmlfile As %String="")
{
    Set ediXMLDoc = ##class(EnsLib.EDI.XML.Document).ImportFromFile(xmlfile,,.sc)
    If $$$ISERR(sc) {
      Quit
    }
    Write ediXMLDoc.GetValueAt("/Patient/PatiendID")
}

[1]

So the answer is:

  • If you want a %XML.Adaptor object, you need a matching class definition. [1][2]
  • If you do not want to create classes and need to parse arbitrary XML, use EnsLib.EDI.XML.Document for dynamic access instead. [1]


Sources:

DC AI Bot · 五月 25 转到文章

你这个现象,检索结果里有两个非常相关的线索:

  1. 在 Linux/Unix 下使用 pyODBC 连接 IRIS 时,如果没有正确设置连接编码,字符串列可能读不出来,表现为空字符串或异常;文档给出的做法是在连接后显式设置 setdecoding / setencoding[1][2]
  2. Linux 下 ODBC 驱动和 ini 配置也容易有问题,尤其是 /etc/odbcinst.ini/etc/odbc.ini 被创建成空文件、驱动文件不对、或者驱动版本与服务端不匹配时,会导致读取异常。[1]

你现在的代码里虽然在 connect() 里传了 charset='UTF-8',但检索到的资料里,针对 pyODBC 的推荐做法不是这个,而是在建立连接后明确设置编码

cnxn.setdecoding(pyodbc.SQL_CHAR, encoding='utf8')
cnxn.setdecoding(pyodbc.SQL_WCHAR, encoding='utf8')
cnxn.setencoding(encoding='utf8')

[1][2]

你可以先把代码改成这样测试:

import pyodbc

driver = '/usr/irisodbc/bin/libirisodbc35.so'
server = 'xx'
database = 'xx'
username = 'xx'
password = 'xx'
port = '51773'

cnxn = pyodbc.connect(
    'DRIVER={InterSystems ODBC35};SERVER=' + server +
    ';PORT=' + port +
    ';DATABASE=' + database +
    ';UID=' + username +
    ';PWD=' + password
)

cnxn.setdecoding(pyodbc.SQL_CHAR, encoding='utf8')
cnxn.setdecoding(pyodbc.SQL_WCHAR, encoding='utf8')
cnxn.setencoding(encoding='utf8')

cursor = cnxn.cursor()
cursor.execute("select top 10 * from xxx.xxx")
row = cursor.fetchone()
while row:
    print(row)
    row = cursor.fetchone()

cnxn.close()

[1][2]

另外,检索结果里还特别提到 Linux 下有几个“坑”,你也建议一起检查:

  • 连接字符串必须严格正确,不能夹杂多余空格。 [1]
  • 需要确认 ODBC 驱动确实被正确识别,可用 pyodbc.drivers() 检查是否能看到驱动名。文档示例输出为 ['InterSystems ODBC35'][1]
  • 需要确认 unixODBC 实际读取的是正确的 odbcinst.iniodbc.ini,可用 odbcinst -j 查看当前生效路径。[1]
  • Linux 安装过程中,/etc/odbcinst.ini/etc/odbc.ini 可能会被创建成空白且无效的文件,这会造成问题,需要删除后重新指向真实配置文件。[1]
  • 驱动文件应存在且权限正确;文档中强调 Linux 下至少要保证这 3 个文件存在且权限正确:
    • libirisodbcu35.so
    • odbcinst.ini
    • odbc.ini [1]
  • 驱动版本最好与远端 IRIS 服务端版本尽量匹配。[1]

你贴的代码里还有一个值得核对的点:你现在直接把 driver 写成了 so 文件路径,但检索结果中的可用示例主要是通过驱动名连接,例如:

DRIVER={InterSystems ODBC35}

并配合 odbcinst.ini 注册驱动。[1][3]

如果你要继续排查,建议按这个顺序做:

  1. 建连后立即加上 setdecoding/setencoding 三行。[1][2]
  2. pyodbc.drivers() 看是否正确注册了 InterSystems ODBC35[1]
  3. odbcinst -j 检查当前实际使用的 /etc/odbcinst.ini/etc/odbc.ini[1]
  4. 检查这两个 ini 文件是不是空的,驱动路径是否正确,so 文件是否存在且有权限。[1]
  5. 尽量使用与服务端接近版本的 IRIS ODBC 驱动。[1]

Note: The reason for the above is, step 3 & 4 would normally create 2 blank (hence invalid) ODBC files in \etc\ directory. Unlike Windows installation, these blanks ini files cause troubles, hence we need to delete them first, then simply re-create a link to the real ini files provided in a mapped Docker volume: /tf/odbcinst.ini, and /tf/odbc.ini [1]

如果你按上面改完后仍然是空字符串,那么从现有检索结果看,下一步最应该优先检查的就是 Linux 端 ODBC 配置文件和驱动注册是否正确,而不是 SQL 本身,因为同样 SQL 在 Windows 正常、Ubuntu 异常,资料里最吻合的是编码和 unixODBC/驱动配置问题。[1][2]


Sources:

DC AI Bot · 五月 25 转到文章

CSP 想做到“不消耗 license”,从现有资料看,不能简单地像你理解的 SOAP 那样一概不消耗;相反,CSP 连接本身属于 license 特殊处理场景,如果不做显式登录,系统会把 web session ID 当作 license user ID,每个 session 都会消耗 1 个 license unit[1]

对于 CSP,资料里给出的建议不是“完全不消耗”,而是通过 %CSP.Session.Login(username,password) 做显式的 license 登录,这样可以避免按 session 粒度反复占用 license;否则一个用户开多个浏览器窗口、或通过多个路径访问时,都可能各自占用 license。[1]

[!NOTE]

When a CSP session ends (from a logout or timeout) and the user has visited only one page, CSP does not immediately release the license. Instead, CSP reserves the license for that user for a grace period of up to 10 minutes.

如果你的目标是让多个请求尽量共用同一个 license,占用更少,现有资料提到可让应用共享同一个会话条件,例如:使用会话、认证访问、两边 web application 配置相同的 GroupById、相同的 cookie path,以及合理的 session timeout。满足这些条件后,“每个 session 只占一个 license slot,并且只登录一次”。[2]

另外,关于你说“Cache 的 webservice SOAP 不消耗 license”,检索到的资料并不是绝对这么说,而是区分场景:

  • 在 IRIS 许可说明中,匿名 SOAP/REST 请求会至少消耗 10 秒 license unit已识别用户的 SOAP/REST 请求也需要 license,因为它被视为 “user request”。[1]
  • 在社区讨论里,有人测试到 Ensemble E-type license 下 SOAP service call 不计 license unit;但 Cache 或 Ensemble C-type license 会计数。[3]

所以,基于现有资料,结论是:

  1. CSP 没有资料表明可以配置成完全不消耗 license。[1]
  2. 对 CSP 能做的是:使用 %CSP.Session.Login() 做显式 license 登录,避免按 session 分散计费。[1]
  3. 如果是前后端/REST/CSP 混合访问,要通过 共享 session/cookie/GroupById 等方式,让多个请求复用同一会话,从而减少 license 占用。[2]
  4. 你看到 SOAP“不消耗”的情况,很可能和 license 类型请求是否被系统识别为真正 SOAP 请求 有关,并不代表 CSP 也能同样配置成 0 消耗。[3][4][5]


Sources:

DC AI Bot · 五月 25 转到文章

从检索结果看,没有找到“Java SDK 通过 ByRef 参数直接创建 MIRROR”的用法说明。能确认到的只有两类相关信息:

  1. Java Native SDK 支持通过 IRISReference 处理 ByRef 参数,以及通过 classMethodObject() 获取返回的对象代理 IRISObject[1][2]
  2. MIRROR 的创建文档里给出的方式是 配置合并/配置 API 或脚本化配置,例如 CreateMirror 会在配置合并时调用 Config.Mirrors.Create();另外也有用 config-api 和脚本自动化创建 mirror 的示例。[3][4]

因此,就当前检索到的资料,没有证据表明可以用 Java IRIS SDK 的 ByRef 参数直接调用某个 SDK 方法来创建 MIRROR[3][4][2]

如果你的目标是“用 Java 发起 MIRROR 创建”,当前资料只支持得出这几点:

  • ByRef 在 Java 中的用途:可以用 IRISReference 作为输出/引用参数传给 ObjectScript 类方法。[1]
  • 如果方法返回对象:Java 可用 iris.classMethodObject(...) 获取 IRISObject 代理对象。[1][2]
  • MIRROR 创建本身:文档显示是通过 CreateMirror 配置操作或 Config.Mirrors.Create() 在配置合并过程中完成,而不是 Java SDK 专门的 MIRROR API。[4]
  • 程序化建镜像的现成方案:检索结果提供的是使用 config-api、JSON 配置文件、Api.Config.Services.Loader.Load(...)SYS.MirrorMaster/SYS.MirrorFailOver 等方式自动化配置 mirror。[3]

如果你现在是在 Java 里调用一个你们自己封装的 ObjectScript 方法,并想把参数做成 ByRef,那么 Java 侧可参考检索结果中的模式:先创建 IRISReference,再把它作为参数传入 classMethodStatusCode(...) 之类的方法。[1]

但如果你问的是:

  • “Java SDK 有没有现成的 CreateMirror/JoinMirror 方法?”
  • “能不能仅靠 ByRef 参数从 Java 直接创建 MIRROR?”

当前检索结果没有提供这样的资料,只能确认 MIRROR 创建资料指向的是配置/脚本方式,而不是 Java SDK 的专用 ByRef 创建接口。[3][4]

如果你愿意,我可以基于你现有的 Java 调用代码 + ObjectScript 方法签名,帮你把它改写成“Java 调用 ObjectScript,由 ObjectScript 再执行 mirror 配置”这种结构;但这一步需要你先贴出你现在调用的类名、方法名和参数定义。


Sources: