搜索​​​​

清除过滤器
文章
Jingwei Wang · 九月 20, 2022

在虚拟化环境中使用镜像的注意事项

在虚拟化环境中使用镜像,构成镜像的InterSystems IRIS实例被安装在虚拟主机上,创造了一个混合的高可用性解决方案,将镜像的优点与虚拟化的优点结合起来。镜像通过自动故障切换对计划内或计划外的故障提供即时响应,而虚拟化HA软件在计划外的机器或操作系统故障后自动重新启动承载镜像成员的虚拟机。这允许失败的成员迅速重新加入镜像,充当备份(或在必要时作为主机)。 当镜像被配置在虚拟化环境中时,请参考以下建议: 故障转移成员的虚拟主机和备机不可以配置在同一台物理机上。 为了避免单点存储故障,故障转移成员上的InterSystems IRIS实例所使用的存储应永久隔离在不同磁盘组或存储阵列的独立数据存储中。 在虚拟化平台层面上进行的一些操作,如备份或迁移,可能会导致故障转移成员长时间没有反应,从而导致不需要的故障转移或不理想的警报频率。为了解决这个问题,你可以增加QoS超时设置。 在进行导致故障转移成员连接中断的计划性维护操作时,你可以暂时停止备份上的镜像,以避免不必要的故障转移和警报。 在镜像成员上必须非常谨慎地使用快照管理,因为将一个成员恢复到早期的快照,既会删除该成员的最新状态(例如,自拍摄快照以来,该成员可能已经从主机变为备机),也会删除其他成员仍然拥有的日志数据。 使用虚拟机备份恢复镜像时请特别注意: 被恢复到早期快照的故障转移成员只能从power-off 状态下恢复;从power-on 状态下恢复会造成两个故障转移成员同时作为主机的可能性。 如果被恢复到早期快照的故障转移成员在没有获得自快照以来创建的所有日志数据的情况下成为主要成员--例如,因为它被迫成为主要成员--所有其他镜像成员必须被重建(如需重建镜像,请联系WRC)。
文章
姚 鑫 · 六月 9, 2021

第二章 从对象写入XML输出

# 第二章 从对象写入XML输出 本章介绍如何从InterSystems IRIS对象生成XML输出。 # 创建XML编写器概述 InterSystems IRIS提供了用于为InterSystems IRIS对象生成`XML`输出的工具。可以指定XML投影的详细信息,如将对象投影到`XML`中所述。然后创建一个`Writer`方法,该方法指定`XML`输出的整体结构:字符编码、对象的显示顺序、是否包括处理指令等。 基本要求如下: - 如果需要特定对象的输出,则该对象的类定义必须扩展`%XML.Adaptor`。除了少数例外,该对象引用的类还必须扩展`%XML.Adaptor`。 - 输出方法必须创建`%XML.Writer`的实例,然后使用该实例的方法。 下面的终端会话显示了一个简单的示例,在该示例中,我们访问启用了XML的对象并为其生成输出: ```java /// d ##class(Sample.Person).Populate(100) /// w ##class(PHA.TEST.Xml).Obj2Xml(1) ClassMethod Obj2Xml(ID) { s obj = ##class(Sample.Person).%OpenId(ID) s xml = ##class(%XML.Writer).%New() s xml.Indent=1 s status = xml.RootObject(obj) q "" } ``` ```java DHC-APP>w ##class(PHA.TEST.Xml).Obj2Xml(1) yaoxin 111-11-1117 1990-04-25 889 Clinton Drive St Louis WI 78672 9619 Ash Avenue Ukiah AL 56589 濮氶懌 111-11-1115 Red Orange Yellow Green Red Orange Yellow 31 ``` # 创建输出方法 输出方法按照指定的顺序逐段构造一个XML文档。输出方法的整体结构取决于需要输出完整的XML文档,还是仅仅输出一个片段。 ## 输出方法的整体结构 方法应按以下顺序执行以下部分或全部操作: 1. 如果使用的对象可能无效,请调用该对象的`%ValidateObject()`方法并检查返回的状态。如果对象无效,则XML也将无效。 **`%XML.Writer` 在导出对象之前不会对其进行验证。这意味着,如果刚刚创建了一个对象,但尚未对其进行验证,则该对象(以及XML)可能是无效的(例如,因为缺少必需的属性)。** 2. 创建`%XML.Writer`类的实例,并根据需要设置其属性。 特别是,需要设置以下属性: - `Indent` 缩进-控制输出是在缩进和换行中生成(如果缩进等于1),还是作为单个长行生成(如果缩进等于0)。后者是默认设置。 - `IndentChars` 缩进字符-指定用于缩进的字符。默认值为两个空格的字符串。如果缩进为0,则此属性无效。 - `Charset` 字符集-指定要使用的字符集。 为了提高可读性,本文档中的示例使用缩进等于1。 3. 指定输出目标。 默认情况下,输出写入当前设备。要指定输出目标,请在开始编写文档之前调用以下方法之一: - `OutputToDevice()`-将输出定向到当前设备。 - `OutputToFile()`-将输出定向到指定文件。可以指定绝对路径或相对路径。请注意,该目录路径必须已经存在。 - `OutputToString()`-将输出定向到字符串。稍后,可以使用另一种方法来检索此字符串。 - `OutputToStream()`-将输出定向到指定的流。 4. 启动文档。可以使用`StartDocument()`方法。请注意,如果尚未通过`StartDocument()`启动文档,则以下方法会隐式启动文档:`Write()`、`WriteDocType()`、`RootElement()`、`WriteComment()`和`WriteProcessingInstruction()`。 5. 可以选择写入文档的序言行。可以使用以下方法: - `WriteDocType()` - 编写DOCTYPE声明。 - `WriteProcessingInstructions()`-编写处理指令。 6. 可以选择指定默认命名空间。编写器将其用于没有定义的XML命名空间的类。 7. 可以选择将命名空间声明添加到根元素。为此,可以在启动根元素之前调用几个实用程序方法。 8. 启动文档的根元素。详细信息取决于该文档的根元素是否对应于InterSystems IRIS对象。有两种可能性: - 根元素可能直接对应于InterSystems IRIS对象。如果要为单个对象生成输出,通常会出现这种情况。 在本例中,使用`RootObject()`方法,该方法将指定的启用XML的对象作为根元素写入。 - 根元素可能只是一组元素的包装器,而这些元素是InterSystems IRIS对象。 在本例中,使用`RootElement()`方法,该方法插入具有指定名称的根级元素。 9. 如果使用`RootElement()`方法,请调用方法来为根元素内的一个或多个元素生成输出。可以按照选择的任何顺序或逻辑在根元素中编写任何元素。有几种方法可以编写单个元素,并且可以结合使用这些技术: - 可以使用`object()`方法,该方法写入启用XML的对象。可以指定此元素的名称,也可以使用由对象定义的默认值。 - 可以使用`element()`方法,该方法使用提供的名称写入元素的开始标记。然后,可以使用`WriteAttribute()`、`WriteChars()`、`WriteCData()`等方法编写内容、属性和子元素。子元素可以是另一个`Element()`,也可以是`Object()`。使用`EndElement()`方法指示元素的结束。 - 可以使用`%XML.Element`并手动构造元素。 10. 如果使用的是`RootElement()`方法,请调用`EndRootElement()`方法。此方法关闭文档的根元素,并根据需要减少缩进(如果有)。 11. 如果文档是从`StartDocument()`开始的,请调用`EndDocument()`方法关闭文档。 12. 如果将输出定向到字符串,请使用`GetXMLString()`方法检索该字符串。 还有许多其他可能的组织,但请注意,某些方法只能在某些上下文中调用。具体地说,一旦开始一个文档,在结束第一个文档之前,不能开始另一个文档。如果这样做,`Writer`方法将返回以下状态: ```java #6275: Cannot output a new XML document or change %XML.Writer properties until the current document is completed. #6275:在当前文档完成之前,无法输出新的XML文档或更改%XML。Writer属性。 ``` `StartDocument()`方法的作用是:显式启动文档。如前所述,其他方法隐式启动文档:`write()`、`WriteDocType()`、`RootElement()`、`WriteComment()`和`WriteProcessingInstruction()`。 注意:这里描述的方法旨在使够向XML文档写入特定的单元,但在某些情况下,可能需要更多的控制。在`%XML.Writer`提供了一个额外的方法`Write()`,可以使用该方法将任意字符串写入输出中的任何位置。 此外,还可以使用`Reset()`方法重新初始化编写器属性和输出方法。如果已经生成了一个XML文档,并且希望在不创建新的编写器实例的情况下生成另一个文档,这将非常有用。 ## 错误检查 **%XML.Writer的大多数方法都会返回状态。应该在每个步骤之后检查状态,并在适当的情况下退出。** ## 插入注释行 如前所述,使用`WriteComment()`方法插入注释行。可以在文档中的任何位置使用此方法。如果尚未启动XML文档,此方法将隐式启动文档。 ## 示例 ```java /// w ##class(PHA.TEST.Xml).Write() ClassMethod Write() As %Status { s obj = ##class(Sample.Person).%OpenId(1) set writer=##class(%XML.Writer).%New() set writer.Indent=1 set status=writer.OutputToDevice() if $$$ISERR(status) { do $System.Status.DisplayError(status) quit $$$ERROR($$$GeneralError, "输出目标无效") } set status=writer.RootObject(obj) if $$$ISERR(status) { do $System.Status.DisplayError(status) quit $$$ERROR($$$GeneralError, "写入根对象时出错") } quit status } ``` 请注意,此方法使用`OutputToDevice()`方法将输出定向到当前设备(默认目标)。这不是必需的,但仅用于演示目的。 当然,输出取决于所使用的类,但可能如下所示: ```java DHC-APP>w ##class(PHA.TEST.Xml).Write() xiaoli 111-11-1111 test 2662 1 ``` ## 有关缩进选项的详细信息 如前所述,可以使用编写器的缩进属性来获取包含附加换行符的输出,以获得更好的可读性。此格式没有正式规范。本节介绍`%XML.Writer`使用的规则。如果缩进等于`1`: - 任何只包含空格字符的元素都会转换为空元素。 - 每个元素都放在自己的行上。 - 如果某个元素是前一个元素的子元素,则该元素相对于该父元素缩进。缩进由`IndentChars`属性确定,该属性默认为两个空格。
文章
Kelly Huang · 七月 19, 2023

单机模式下 EMPI 的安装和适配 - FHIR之配置篇

在之前的文章中,我们已经了解了如何配置和自定义我们的 EMPI,我们已经了解了如何通过 HL7 消息传递将新患者纳入我们的系统中,但当然,并不是所有的东西都是 HL7 v.2!我们如何配置 EMPI 实例以使用 FHIR 消息传递? 什么是FHIR? 对于那些不太熟悉 FHIR 这个术语的人,只需指出它们是Fast Healthcare Interoperability Resource的首字母缩写即可。 FHIR是HL7制定的医疗保健互操作性标准,基于JSON格式和REST通信,建立了一系列不同类型信息(患者数据、医院中心、诊断、医疗预约......)的“资源”。您可以在他们的官方页面上查看所有这些资源 InterSystems 和 FHIR 从 InterSystems 中,我们了解到 FHIR 为医疗保健互操作性领域提供的实用程序,因此我们拥有广泛的产品和功能,使我们能够充分利用并发挥其全部潜力: InterSystems FHIR 服务器允许存储和管理 FHIR 资源。 InterSystems FHIR SQL Builder使用户可以通过 SQL 查询使用 FHIR 存储库中存储的所有信息。 InterSystems FHIR 适配器允许在我们的产品中发送和接收 FHIR 消息。 好吧,在我们的文章中,我们将利用适配器提供的功能来接收 FHIR 消息。 EMPI配置 FHIR 适配器安装 我们首先回顾一下独立 EMPI 安装中有哪些服务: 正如您所看到的,在独立模式下安装 EMPI 后,创建了一个注册表服务,其中包含配置 EMPI 所需的所有选项,以及我们称为 HSPIDATA 的命名空间,其中我们有一个产品来管理互操作性功能,需要。 对于我们的案例,我们创建了一个名为 WEBINAR 的新命名空间,我们将在其中部署正常的互操作性生产。我们将在此命名空间中安装FHIR 互操作性适配器,此安装将发布一个 Web 应用程序,我们将向该应用程序发送带有 FHIR 消息的 REST 调用。为此,我们将从终端执行以下命令: zn "WEBINAR" set status = ##class (HS.FHIRServer.Installer).InteropAdapterConfig( "/csp/healthshare/webinar/fhir/r4" ) 安装后,我们将检查从管理门户创建的新应用程序: 让我们在 WEBINAR 命名空间中检查我们的生产: 默认创建了两个新的业务组件: InteropService :在我们的生产中,我们直接使用其类 HS.FHIRServer.Interop.Service 的名称,它将是负责接收发送到我们端点的 FHIR 消息的业务服务。 /csp/healthshare/网络研讨会/fhir/r4 InteropOperation :我们已将此名称更改为 HS.FHIRServer.Interop.Operation。对于我们的示例,我们忽略了此业务操作,因为我们将通过 TCP 将 FHIR 消息的信息发送到我们的 EMPI 生产 创建业务组件来管理 FHIR 消息传递 很好,我们已经启用了业务服务来接收 FHIR 消息,现在我们感兴趣的是从消息中提取信息并将其发送到我们的 EMPI 生产。让我们看一下 HS.FHIRServer.Interop.Service 将接收的消息类型: Include HS.FHIRServer /// FHIRServer REST Business Service Class HS.FHIRServer.Interop.Service Extends (Ens.BusinessService, HS.HC.Util.Trace.Helper) { Parameter SETTINGS = "TargetConfigName:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},Timeout:Basic" ; /// Configuration item to which to send inbound messages. Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIRServer.Interop.Operation" ] ; /// Timeout for dispatch (so we don't hold up the HTTP service too long or hang up a production shutdown). Property Timeout As %Integer [ InitialExpression = 25 ] ; /// Process an incoming message into the production; dispatch it to the configured target. /// The Interoperability contract requires that errors be returned as %Status here. Method OnProcessInput(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status 该消息的类型为 HS.FHIRServer.Interop.Request,如果我们查阅文档,我们将看到它有一个名为QuickStreamId的关联属性,我们将使用该属性来提取关联的 FHIR 消息。为此,我们将业务服务中收到的消息重定向到业务流程Webinar.BP.FHIRToHubRequest 该业务流程仅具有将消息重定向到负责通过 TCP 将消息发送到 EMPI 生产的业务操作FromFHIRToMPI的功能。 Method OnRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { Set tSC = ..SendRequestSync ( "FromFHIRToMPI" , pRequest, .pResponse) Quit $$$OK } 让我们看一下Webinar.BO.ToMPI类,并解释它如何从 FHIR 获取消息并将其转换为我们将发送到 EMPI 生产的字符串: Include HS.FHIRServer Class Webinar.BO.ToMPI Extends Ens.BusinessOperation { Parameter ADAPTER = "EnsLib.TCP.CountedOutboundAdapter" ; Property Adapter As EnsLib.TCP.CountedOutboundAdapter ; Parameter INVOCATION = "Queue" ; Parameter SETTINGS = "FHIRMetadataSet::selector?context={HS.FHIRServer.Util.ContextSearch/FHIRMetadataSets}" ; /// FHIR Metadata Set. These are defined in HS_FHIRServer.FHIRMetadataSet. Property FHIRMetadataSet As %String (MAXLEN = 256 ) ; Method SendFHIRRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { // Get version of FHIR message configured Set tFHIRMetadataSetKey = $ZStrip ( $Piece ( ..FHIRMetadataSet , "/" , 1 ), "<>W" ) // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'= "" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class (HS.SDA3.QuickStream). %OpenId (pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'= "" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError ( ##class (HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat= "json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat= "xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj (tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (( $IsObject (pRequest.Request.Json))&&(pRequest.Request.Json. %GetIterator (). %GetNext ())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj (pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus ( $$$ERROR ( $$$GeneralError , "FHIR interop request message missing FHIR content" )) } // Transform Dynamic Object to string set tRequest = tDynObj. %ToJSON () // Send message to EMPI production Set tSC= ..Adapter .SendMessageString(tRequest,.tResponse) $$$ThrowOnError (pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC } ClassMethod GetDynObj(stream As %Stream.Object , fhirVersion As %String , fhirFormat As %String ) As %DynamicObject { set schema = ##class (HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}. %FromJSON (stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class (HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } XData MessageMap { <MapItems> <MapItem MessageType= "HS.FHIRServer.Interop.Request" > <Method>SendFHIRRequest</Method> </MapItem> </MapItems> } } 让我们一步步分析我们的类: 第一点是了解我们将在业务运营选项中配置的 FHIR 消息的版本。 Set tFHIRMetadataSetKey = $ZStrip ( $Piece ( ..FHIRMetadataSet , "/" , 1 ), "<>W" ) 我们恢复正在处理的 FHIR 消息的类型,在我们的例子中它将是 R4。 接下来我们提取FHIR消息的内容: // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'= "" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class (HS.SDA3.QuickStream). %OpenId (pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'= "" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError ( ##class (HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat= "json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat= "xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj (tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (( $IsObject (pRequest.Request.Json))&&(pRequest.Request.Json. %GetIterator (). %GetNext ())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj (pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus ( $$$ERROR ( $$$GeneralError , "FHIR interop request message missing FHIR content" )) } 我们从 FHIR 消息中获取 %DynamicObject: ClassMethod GetDynObj(stream As %Stream.Object , fhirVersion As %String , fhirFormat As %String ) As %DynamicObject { set schema = ##class (HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}. %FromJSON (stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class (HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } 最后,我们将其转换为字符串,并通过 TCP 将其发送到我们的 EMPI 生产,向向我们发送原始消息的系统返回 200: // Transform Dynamic Object to string set tRequest = tDynObj. %ToJSON () // Send message to EMPI production Set tSC= ..Adapter .SendMessageString(tRequest,.tResponse) $$$ThrowOnError (pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC 好吧,我们已经将消息发送到我们的 EMPI 生产中。让我们看一下示例消息的跟踪: 在下一篇文章中,我们将检索提交的字符串,将其转换回 %DynamicObject 并向 EMPI 提供数据以使用新数据执行注册/更新操作。 希望这些内容对你有用! 致敬原创作者 @Luis Angel
文章
姚 鑫 · 七月 6, 2021

第二十九章 从XML架构生成类

# 第二十九章 从XML架构生成类 Studio提供了一个向导,该向导读取XML模式(从文件或URL),并生成一组支持XML的类,这些类对应于模式中定义的类型。 所有的类都扩展`%XML.Adaptor`。 指定一个包来包含类,以及控制类定义细节的各种选项。 向导还可以作为类方法使用,也可以使用该类方法。 在内部,SOAP向导在读取WSDL文档并生成web客户端或web服务时使用此方法; 注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。 # 使用向导 要使用XML架构向导,请执行以下操作: 1. 选择 Tools > Add-Ins > XML Schema Wizard. ![image](/sites/default/files/inline/images/1_50.png) 2. 在第一个屏幕上,指定要使用的XML模式。 做以下其中一项: - 对于模式文件Schema File,选择Browse 以选择XML模式文件。 - 对于URL,指定模式的URL。 ![image](/sites/default/files/inline/images/2_30.png) 3. 选择Next。 下一个屏幕显示模式,以便可以验证选择了正确的模式。 ![image](/sites/default/files/inline/images/3_24.png) 4. 可选择以下选项: - 保留空类Keep Empty Classes,它指定是否保留没有属性的未使用的类。 如果选择此选项,则不会在向导结束时删除此类; 否则,将删除它们。 - “不创建数组属性”Create No Array Properties控制向导是否生成数组属性。 如果选择此选项,向导不会生成数组属性,而是生成另一个表单。 - 为可为空的元素生成XMLNIL属性参数,它控制向导是否为生成的类中适用的属性指定XMLNIL属性参数。 该选项适用于每个对应于用`nillable="true"`指定的XML元素的属性。 如果选择此选项,向导将向属性定义添加`XMLNIL=1`。 否则不添加该参数。 该参数的详细信息请参见将对象投影到XML中的“处理空字符串和空值”。 - 为可为空的元素生成`XMLNILNOOBJECT`属性参数,它控制向导是否为生成的类中适用的属性指定`XMLNILNOOBJECT`属性参数。 该选项适用于每个对应于用`nillable="true"`指定的XML元素的属性。 如果选择此选项,向导将向属性定义添加`XMLNILNOOBJECT=1`。 否则不添加该参数。 该参数的详细信息请参见将对象投影到XML中的“处理空字符串和空值”。 5. 选择Next。 下一个屏幕显示关于要生成的类的选项的一些基本信息。 6. 在这个屏幕上,指定以下选项: - 如果希望向导编译生成的类,可以选择“编译生成的类”。 - 可选择“添加NAMESPACE类参数”来指定`NAMESPACE`参数。 在本例中,`NAMESPACE`被设置为模式中`targetNamespace`的值。 如果不设置此选项,则不指定`NAMESPACE`。 建议在所有情况下都选择这个选项,因为每个支持XML的类都应该分配给一个XML名称空间。 (但是,为了向后兼容,可以将此选项清除。) - 如果希望生成的类是持久类,请选择Create persistent classes。然后类扩展`%Persistent`。 可以稍后在向导中针对各个类更改这一点。 - 如果生成持久类,可以选择如何处理由另一个` b`的``组成的`` a。当向导生成一个包含属性`a`的持久类时,该属性有三种可能的形式。 可以将其定义为对象列表、一对多关系(默认)或父子关系。 下表总结了这些选择: 在持久性类中为集合属性使用关系 |向多对关系添加索引 |使用父子关系|生成的属性A的形式 ---|---|---|--- selected (default)| not selected| not selected| 无索引的一对多关系 selected (default) |selected| not selected| 在多侧与索引的一对多关系 selected (default) |如果选择Use parent-child relationship,则忽略此选项| selected| 父子关系 not selected| not selected| not selected |List of objects 此外,如果未选择使用父子关系,则可以选择将`%OnDelete`方法添加到类以级联删除。如果选择此选项,当向导生成类定义时,它会在这些类中包含`%OnDelete()`回调方法的实现。生成的`%OnDelete()`方法删除类引用的所有持久对象。如确实选择了使用父子关系,请不要选择此选项;父子关系已经提供了类似的逻辑。 注意:如果修改生成的类,请确保根据需要修改`%OnDelete()`回调方法。 如果生成持久类,向导可以向每个对象类型类添加临时属性,以便可以为对象投影InterSystems IRIS内部标识符。选项如下: - None-如果选择此选项,向导不会添加此处描述的任何属性。 - Use Id -如果选择此选项,向导将向每个对象类型类添加以下属性: ``` Property %identity As %XML.Id (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient]; ``` - Use Oid -如果选择此选项,向导将向每个对象类型类添加以下属性: ``` Property %identity As %XML.Oid (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient]; ``` - Use GUID-如果选择此选项,向导将向每个对象类型类添加以下属性: ``` Property %identity As %XML.GUID (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient]; ``` 底部的表格列出了模式中的XML名称空间。在这里,指定包含该行中显示的XML名称空间的类的包。要执行此操作,请在程序包名字段中为该行指定程序包名。 7. 选择下一步。 8. 在下一个屏幕上,指定以下选项: - Java Enabled - 如果选择此选项,则每个类都包括一个Java映射。 - Data Population数据填充-如果选择此选项,则除`%XML.Adaptor`外,每个类还继承会`%Populate`。 - SQL Column Order-如果选择此选项,每个属性将为`SqlColumnNumber`关键字指定一个值,以便属性在SQL中的顺序与它们在架构中的顺序相同。 - No Sequence Check-如果选中此选项,向导将生成的类中的`XMLSEQUENCE`参数设置为0。在某些情况下,如果XML文件的元素顺序与XML架构不同,则此选项非常有用。 默认情况下,`XMLSEQUENCE`参数在生成的类中设置为1。这可确保属性以与架构中相同的顺序包含在类定义中。 - XMLIGNORENULL-如果选择此选项,向导会将`XMLIGNORENULL=1`添加到类定义中。否则,它不会添加此参数。 - 将流用于二进制Use Streams for Binary - 如果选择此选项,向导将为`xsd:base64Binary`类型的任何元素生成`%Stream.GlobalBinary`类型的属性。如果清除此选项,则该属性的类型为`%xsd.base64Binary`。 请注意,向导将忽略`xsd:base64Binary`类型的任何属性。 - 在复选框下方,该表列出了向导将生成的类。对于每个类,确保适当地设置了`Extensions/Type`。在此,可以选择以下选项之一: - 持久类`Persistent` -如果选择此选项,则类是持久性类。 - `Serial`-如果选择此选项,则类为序列类。 - `Registered Object`-如果选择此选项,则类为注册对象类。 所有生成的类还扩展`%XML.Adaptor`。 - 在表的右列中,为每个应编制索引的属性选择索引。 9. 选择Finish(完成)。 然后,向导将生成这些类,并在需要时编译它们。 对于这些类的属性,如果架构中相应元素的名称以下划线(_)开头,则属性名称以百分号(%)开头。 # 以编程方式生成类 XML架构向导也可用作`%XML.Utils.SchemaReader`类的`process()`方法。要使用此方法,请执行以下操作: 1. 创建`%XML.Utils.SchemaReader`的实例。 2. 可以选择设置实例的属性以控制其行为。 3. 可以选择创建InterSystems IRIS多维数组,以包含有关其他设置的信息。 4. 调用实例的`process()`方法: ``` method Process(LocationURL As %String, Package As %String = "Test", ByRef Features As %String) as %Status ``` - LocationURL必须是架构的URL或架构文件的名称(包括其完整路径)。 - Package是用于放置生成的类的包的名称。如果不指定程序包,InterSystems IRIS将使用服务名称作为程序包名称。 - Feature是在上一步中选择创建的多维数组。 # 每种XSD类型的默认IRIS数据类型 对于它生成的每个属性,XML架构向导会根据架构中指定的XSD类型自动使用适当的InterSystems IRIS数据类型类。下表列出了XSD类型和相应的InterSystems IRIS数据类型: 用于XML类型的InterSystems IRIS数据类型 源文档中的XSD类型 |生成的IRIS类中的数据类型 ---|--- anyURI | %xsd.anyURI base64Binary | `%xsd.base64Binary`或`%Stream.GlobalBinary`,具体取决于选择的选项。确定每个字符串是否可能超出字符串长度限制,如果可能,则将生成的属性从`%xsd.base64Binary`修改为适当的流类。) boolean | `%Boolean` byte | `%xsd.byte` date | `%Date` dateTime | `%TimeStamp` decimal |`%Numeric` double | `%xsd.double` float | `%xsd.float` hexBinary | `%xsd.hexBinary` int |`%xsd.int` integer |`%Integer` long | `%Integer` negativeInteger | `%xsd.negativeIntege` nonNegativeInteger | `%xsd.nonNegativeInteger` nonPositiveInteger |`%xsd.nonPositiveInteger` positiveInteger | `%xsd.positiveInteger` short | `%xsd.short` string | `%String` (注意:责任确定每个字符串是否可能超出字符串长度限制,如果可能,则将生成的类型修改为适当的流类。) time | `%Time` unsignedByte | `%xsd.unsignedByte` unsignedInt |`%xsd.unsignedInt` unsignedLong | `%xsd.unsignedLong` unsignedShort | `%xsd.unsignedShort` no type given | `%String` # 生成的属性的属性关键字 对于它生成的每个属性,XML架构向导还使用架构中的信息自动设置以下关键字: - Description - Required - ReadOnly (如果相应的元素或属性是用固定属性定义的) - InitialExpression (该值取自架构中的固定属性) - Keywords related to relationships # 生成的属性的参数 对于它生成的每个属性,XML架构向导会根据需要自动设置`XMLNAME`、`XMLPROJECTION`和所有其他与XML相关的参数。它还根据需要设置其他参数,如`MAXVAL`、`MINVAL`和`VALUELIST`。 # 调整为超长字符串生成的类 在极少数情况下,可能需要编辑生成的类来容纳超长的字符串或二进制值,超出字符串长度限制。 对于任何字符串类型,XML架构都不包含任何指示字符串长度的信息。XML架构向导将所有字符串值映射到InterSystems IRIS `%String`类,并将所有`base64Binary`值映射到`%xsd.base64Binary`类。这些选择可能不合适,具体取决于类要承载的数据。 在使用生成的类之前,应该执行以下操作: - 检查生成的类,找到定义为`%string`或`%xsd.base64Binary`的属性。考虑将在其中使用这些类的上下文,特别是这些属性。 - 如果认为`%string`属性可能需要包含超出字符串长度限制的字符串,请将该属性重新定义为适当的字符流。同样,如果认为`%xsd.base64Binary`属性可能需要包含超过相同限制的字符串,请将该属性重新定义为适当的二进制流。 - 另请注意,对于类型为`%string`、`%xsd.string`和`%BINARY`的属性,默认情况下,`MAXLEN`属性参数为`50`个字符。可能需要指定更高的限制才能进行正确的验证。 (对于`%xsd.base64Binary`类型的属性,`MAXLEN`为`“”`,这意味着不会通过验证检查长度。但是,字符串长度限制确实适用。)
文章
姚 鑫 · 六月 25, 2021

第十八章 签署XML文档

# 第十八章 签署XML文档 本章介绍如何向XML文档添加数字签名。 # 关于数字签名文档 数字签名的XML文档包括一个或多个``元素,每个元素都是数字签名。 每个``元素对文档中的特定元素进行如下签名: - 每个签名元素都有一个ID属性,该属性等于某个唯一值。例如: ```xml ``` - 一个``元素包含一个``元素,它指向该Id,如下所示: ```xml ``` ``元素是由私钥签名的。此元素包括由签名机构签署的X.509证书。如果已签名文档的接收方信任此签名机构,则接收方可以验证证书,并使用包含的公钥验证签名。 注意: IRIS还支持一种变体,其中有签名的元素有一个名为ID的属性,而不是ID。 下面是一个示例,为了便于阅读,添加了空格: ```xml Persephone MacMillan 1976-02-20 FHwW2U58bztLI4cIE/mp+nsBNZg= MTha3zLoj8Tg content omitted MIICnDCCAYQCAWUwDQYJ content omitted ``` 要创建数字签名,可以使用类`%XML.Security.Signature`。 这是一个支持xml的类,它的投影是适当名称空间中的有效``元素。 # 创建数字签名XML文档 要创建数字签名的XML文档,请使用`%XML.Writer`为一个或多个适当定义的启用了XML的对象生成输出。 在为对象生成输出之前,必须创建所需的签名并将其写入对象,以便可以将信息写入目标。 ## 签名的前提条件 在签署文档之前,必须至少创建一个IRIS凭据集。InterSystems IRIS凭据集是存储在系统管理器数据库中的以下信息集的别名: - 包含公钥的证书。证书应由文档接收者信任的签名机构签名。 - 关联的私钥, IRIS在需要时使用,但从不发送。签名需要私钥。 - (可选)私钥的密码, IRIS在需要时使用私钥,但从不发送。可以加载私钥,也可以在运行时提供私钥。 ## 启用XML的类的要求 启用XML的类必须包括以下内容: - 投影为ID属性的特性。 - 至少一个类型为`%XML.Security`的属性。投影为``元素的签名。(一个XML文档可以包含多个``元素。) 考虑以下类: ```java Class XMLEncryption.Simple Extends (%RegisteredObject, %XML.Adaptor) { Parameter NAMESPACE = "http://mynamespace"; Parameter XMLNAME = "Person"; Property Name As %String; Property DOB As %String; Property PersonId As %String(XMLNAME = "Id", XMLPROJECTION = "ATTRIBUTE"); Property MySig As %XML.Security.Signature(XMLNAME = "Signature"); } ``` ## 生成和添加签名 要生成和添加数字签名,请执行以下步骤: 1. 可以选择包含`%soap.inc`包含文件,该文件定义可能需要使用的宏。 2. 创建`%SYS.X509Credentials`的实例在访问相应InterSystems IRIS凭据集。为此,调用`%SYS.X509Credentials`的`GetByAlias()`类方法。 ```java classmethod GetByAlias(alias As %String, pwd As %String) as %SYS.X509Credentials ``` - alias 别名是证书的别名。 - pwd 是私钥密码。仅当关联的私钥已加密并且在加载私钥文件时未加载密码时,才需要私钥密码。 若要运行此方法,必须以该凭据集的`OwnerList`中包含的用户身份登录,否则`OwnerList`必须为空。 3. 在使用给定凭据集创建 `%XML.Security.Signature`的实例。为此,请调用该类的`Createx509()`类方法: ```java classmethod CreateX509(credentials As %SYS.X509Credentials, signatureOption As %Integer, referenceOption As %Integer) as %XML.Security.Signature ``` - `credentials` 凭据是刚刚创建`%SYS.X509Credentials`的实例。 - `signatureOption`是`$$$SOAPWSIncludeNone`(还有其他选项,但它们不适用于此方案) - `referenceOption` 指定对符号元素的引用的性质。 这里使用的宏在`%soap.inc`中定义包括文件。 4. 获取ID属性的值,对于此签名将点的ID。此详细信息取决于启用XML对象的定义。 5. 创建`%XML.Security.Reference`的实例,指向该ID。为此,请调用该类的`Create()`类方法: ```java ClassMethod Create(id As %String, algorithm As %String, prefixList As %String) ``` - `id`是该参考应该指向的ID。 - `algorithm` 算法应该是以下之一: - `$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSexcc14`n — 使用此版本获取独占规范化。 - `$$$SOAPWSEnvelopedSignature` — 这相当于前面的选项。 - `$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSexcc14n` — 使用此版本进行包容性规范化。 6. 对于签名对象,调用`AddReference()`方法将此引用添加到签名: ```java Method AddReference(reference As %XML.Security.Reference) ``` 7. 更新启用XML的类的相应属性以包含签名。 ```java set object.MySig=signature ``` 8. 创建`%XML.Document`的实例,该实例包含序列化为XML的启用了XML的对象。 这是必要的,因为签名必须包括有关签名文档的信息。 注意:本文档不包含空格。 9. 调用签名对象的`SignDocument()`方法: ```java Method SignDocument(document As %XML.Document) As %Status ``` 此方法的参数是刚刚创建的中`%XML.Document`的实例。`SignDocument()`方法使用该实例中的信息更新签名对象。 10. 使用`%XML.Writer`中为对象生成输出。 注意:生成的输出必须包含与签名中使用的文档相同的空格(或不包含空格)。签名包含文档的摘要,如果将编写器中的缩进属性设置为1,则摘要将与文档不匹配。 例如: **放入到对应的实体类中,有一些属性需要替换** ```java Method WriteSigned(filename As %String = "") { #Include %soap //创建签名对象 set cred=##class(%SYS.X509Credentials).GetByAlias("servercred") set parts=$$$SOAPWSIncludeNone set ref=$$$KeyInfoX509Certificate set signature=##class(%XML.Security.Signature).CreateX509(cred,parts,ref,.status) if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit} // 获取我们要签名的元素的ID属性; set refid=$this.PersonId ; 此详细信息取决于类的结构 // 然后在签名对象中创建对该ID的引用 set algorithm=$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSc14n set reference=##class(%XML.Security.Reference).Create(refid,algorithm) do signature.AddReference(reference) //设置MySig属性,以便$this具有我们为其生成输出时所需的所有信息 set $this.MySig=signature ; 此详细信息取决于类的结构 //除了$this之外,我们还需要%XML.Document的一个实例,该实例包含序列化为XML的对象 set document=..GetXMLDoc($this) //使用序列化的XML对象对文档进行签名,这将更新部分签名 set status=signature.SignDocument(document) if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit} // 写入对象的输出 set writer=##class(%XML.Writer).%New() if (filename'="") { set status=writer.OutputToFile(filename) if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit} } do writer.RootObject($this) } ``` 前面的实例方法使用以下泛型类方法,该方法可以与任何启用了XML的对象一起使用: ```java ClassMethod GetXMLDoc2(object) As %XML.Document { //步骤1-将对象作为XML写入流 set writer=##class(%XML.Writer).%New() set stream=##class(%GlobalCharacterStream).%New() set status=writer.OutputToStream(stream) if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF} set status=writer.RootObject(object) if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF} //步骤2-从流中提取%XML.Document set status=##class(%XML.Document).GetDocumentFromStream(stream,.document) if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF} quit document } ``` 变体:引用中带有`URI=""`的数字签名 作为一种变体,签名的``元素可以具有`URI=""`,这是对包含签名的XML文档根节点的引用。 要通过以下方式创建数字签名: 1. 可以选择包含`%soap.inc`包含文件,该文件定义可能需要使用的宏。 2. 创建`%SYS.X509Credentials`的实例在访问相应InterSystems IRIS凭据集。为此,请调用`%SYS.X509Credentials`的`GetByAlias()`类方法,如前面的步骤所述。 3. 创建使用给定凭据集的`%XML.Security.Signature`的实例。为此,请调用该类的`CreateX509()`类方法,如前面的步骤所述。 4. 按如下方式创建`%XML.Security.X509Data`的实例: ```java set valuetype=$$$KeyInfoX509SubjectName_","_$$$KeyInfoX509Certificate set x509data=##class(%XML.Security.X509Data).Create(valuetype,cred) ``` 其中,`cred`是`%SYS`的实例。 `x509credentials`在之前创建的新窗口中打开。 这些步骤创建了一个``元素,其中包含一个``元素和一个``元素。 5. 将``元素添加到签名的``元素中,方法如下: ```java do signature.KeyInfo.KeyInfoClauseList.Insert(x509data) ``` 其中签名是`%XML.Security`的实例。 `x509data`是`%XML.Security.X509Data`的实例。 6. 创建`%XML.Security`的实例。 参考如下: ```java set algorithm=$$$SOAPWSEnvelopedSignature set reference=##class(%XML.Security.Reference).Create("",algorithm) ``` 7. 在步骤6(调用`AddReference()`)中继续上述步骤。 # 验证数字签名 对于收到的任何数字签名文档,都可以验证签名。不需要具有与文档内容匹配的启用XML的类。 ## 验证签名的前提条件 若要验证数字签名,必须首先为签名者向InterSystems IRIS提供受信任的证书。如果InterSystems IRIS可以验证签名者的证书链(从签名者自己的证书到来自InterSystems IRIS信任的证书颁发机构(CA)的自签名证书),包括中间证书(如果有),则InterSystems IRIS可以验证签名。 ## 验证签名 要验证数字签名的XML文档中的签名,请执行以下操作: 1. 创建`%XML.Reader`的实例并使用它打开文档。 2. 获取阅读器的`Document`属性。这是 `%XML.Document`的一个实例。包含作为`DOM的XML文档的文档 3. 使用阅读器的`correlation()`方法将``元素或元素与类`%XML.Security.Signature`关联起来。 例如: ```java do reader.Correlate("Signature","%XML.Security.Signature") ``` 4. 遍历文档以读取``元素或多个元素。为此,可以使用阅读器的`Next()`方法,该方法通过引用返回一个导入的对象(如果有的话)。 例如: ```java if 'reader.Next(.isig,.status) { write !,"Unable to import signature",! do $system.OBJ.DisplayError(status) quit } ``` 导入的对象是`%XML.Security.Signature`的实例 5. 调用导入签名的`ValidateDocument()`方法。该方法的参数必须是`%XML`的实例。先前检索到的文档。 ```java set status=isig.ValidateDocument(document) ``` 例如: ```java ClassMethod ValidateDoc(filename As %String) { set reader=##class(%XML.Reader).%New() set status=reader.OpenFile(filename) if $$$ISERR(status) {do $System.Status.DisplayError(status) quit } set document=reader.Document //获取 元素 //假设只有一个签名 do reader.Correlate("Signature","%XML.Security.Signature") if 'reader.Next(.isig,.status) { write !,"无法导入签名",! do $system.OBJ.DisplayError(status) quit } set status=isig.ValidateDocument(document) if $$$ISERR(status) {do $System.Status.DisplayError(status) quit } } ``` # 变体:引用ID的数字签名 在典型的情况下,``元素包含一个``元素,该元素指向文档中其他地方的唯一Id。 InterSystems IRIS还支持一种变体,其中``元素指向名为ID(而不是ID)的属性。 在这种变体中,需要额外的工作来签署文档和验证文档。 要对文档进行数字签名,请遵循“创建数字签名XML文档”中的步骤,并进行以下更改: - 对于支持xml的类,包含一个作为ID属性而不是ID属性投影的属性。 - 在生成和添加签名时,调用`%XML`的`AddIDs()`方法。文档实例。 在获得序列化的XML文档之后,在调用签名对象的`SignDocument()`方法之前,执行此操作。 例如: ```java //设置MySig属性,使$this在为其生成输出时拥有所需的所有信息 set $this.MySig=signature ; 这个细节取决于类的结构 //除了$this之外,我们还需要%XML的实例 包含序列化为XML的对象的文档 set document=..GetXMLDoc($this) //***** 当签名引用ID属性时添加步骤 ***** do document.AddIDs() //使用序列化的XML对象对文档进行签名,这更新了部分签名 set status=signature.SignDocument(document) if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit} ``` - 当验证文档时,在调用`Correlate()`之前包含以下步骤: 1. 调用 `%XML.Document`的`AddIDs()`方法。 2. 调用XML阅读器的`Rewind()`方法。 例如: ``` set document=reader.Document //添加签名引用ID属性时的步骤 do document.AddIDs() do reader.Rewind() //获取 元素 do reader.Correlate("Signature","%XML.Security.Signature") ```
文章
姚 鑫 · 四月 13, 2021

第二章 定义和构建索引(一)

# 第二章 定义和构建索引(一) # 概述 索引是由持久类维护的结构,InterSystems IRIS®数据平台可以使用它来优化查询和其他操作。 可以在表中的字段值或类中的相应属性上定义索引。(还可以在多个字段/属性的组合值上定义索引。)。无论是使用SQL字段和表语法还是类属性语法定义相同的索引,都会创建相同的索引。当定义了某些类型的字段(属性)时,InterSystems IRIS会自动定义索引。可以在存储数据或可以可靠派生数据的任何字段上定义附加索引。InterSystems IRIS提供了几种类型的索引。可以为同一字段(属性)定义多个索引,为不同的目的提供不同类型的索引。 无论是使用SQL字段和表语法,还是使用类属性语法,只要对数据库执行数据插入、更新或删除操作,InterSystems IRIS就会填充和维护索引(默认情况下)。可以覆盖此默认值(通过使用`%NOINDEX`关键字)来快速更改数据,然后作为单独的操作生成或重新生成相应的索引。可以在用数据填充表之前定义索引。还可以为已经填充了数据的表定义索引,然后作为单独的操作填充(构建)索引。 InterSystems IRIS在准备和执行SQL查询时使用可用的索引。**默认情况下,它选择使用哪些索引来优化查询性能。** 可以根据需要覆盖此默认值,以防止对特定查询或所有查询使用一个或多个索引。 ## 索引属性 每个索引都有一个唯一的名称。此名称用于数据库管理目的(报告、索引构建、删除索引等)。**与其他SQL实体一样,索引同时具有SQL索引名和相应的索引属性名;这些名称在允许的字符、区分大小写和最大长度方面有所不同。如果使用`SQL CREATE INDEX`命令定义,系统将生成相应的索引属性名称。如果使用持久类定义进行定义,则`SqlName`关键字允许用户指定不同的SQL索引名(SQL映射名称)。** Management Portal SQL界面的Catalog Details显示每个索引的SQL索引名称(SQL映射名称)和相应的索引属性名称(索引名称)。 索引类型由两个索引类关键字`Type`和`Extent`定义。IRIS提供的索引类型包括: - 标准索引(`Type = index`)——一个持久数组,它将索引值与包含该值的行的 RowID相关联。 **任何没有明确定义为位图索引、位片索引或区段索引的索引都是标准索引。** - 位图索引(`Type = Bitmap`)——一种特殊的索引,使用一系列位字符串来表示与给定索引值对应的RowID值集; InterSystems IRIS包括许多位图索引的性能优化。 - 位片索引(`Type = Bitslice`)——一种特殊的索引,能够非常快速地计算某些表达式,例如总和数和范围条件。 某些SQL查询自动使用位片索引。 - 区段索引(`Extent Indices`)——一个区段中所有对象的索引。 有关更多信息,请参阅类定义参考中的区段索引关键字页。 - 范围索引-范围中所有对象的索引。 **表(类)的最大索引数为400。** ## 存储类型和索引 这里描述的索引功能适用于存储在持久化类中的数据。 InterSystems SQL支持使用InterSystems IRIS默认存储结构存储的数据的索引功能:`%Storage.Persistent`(`%Storage.Persistent`映射类)。 InterSystems SQL还支持使用`%Storage.SQL`(`%Storage.SQL`映射的类)存储的数据的索引功能。可以使用函数索引类型为`%Storage.SQL`映射的类定义索引。索引的定义方式与使用默认存储的类中的索引相同,但有以下特殊注意事项: - 如果`IdKey`函数索引不是系统自动分配的,则该类必须定义`IdKey`函数索引。 - 此功能索引必须定义为索引。 请注意,不应直接调用`%Storage.Persistent`和`%Storage.SQL`类方法。相反,应该使用`%Persistent`类方法和本章中描述的操作调用索引功能。 ## 索引全局名称 使用以下两种策略之一生成用于存储索引数据的下标全局: - `%CLASSPARAMETER USEEXTENTSET=0`使用全局命名策略创建由用户指定的名称、附加的字母代码和索引名称组成的“传统”全局名称。用户可以理解这些全局名称,但它们可能很长,并且效率低于散列的全局名称。 - 如果`USEEXTENTSET=0`且未指定`DEFAULTGLOBAL`,则以下示例将描述生成的全局名称:`Sample.MyTest`持久类将定义名为`^Sample.MyTestD`的master map全局名`^Sample.MyTestD`位图范围索引全局名`^Sample.MyTestI(“$MyTest”)`(或`^Sample.MyTestI(“DDLBEIndex”)`),并且对于定义的索引`NameIDX`,它将定义名为`^Sample.MyTestI(“DDLBEIndex”)`的全局名。请注意,这些全局变量指定的是持久性类名(区分大小写),而不是SQL表名。 - 如果`USEEXTENTSET=0`并指定了`DEFAULTGLOBAL`,则指定的全局名称将替换永久类名。这允许指定一个比持久类名称更短或更清晰的名称。例如,如果`DEFAULTGLOBAL=“MyGlobal”`,则全局变量的名称如下:`^MyGlobalD`和`^MyGlobalI(“NameIDX”)`。 - `%CLASSPARAMETER USEEXTENTSET=1`使用创建哈希全局名称的全局命名策略。这包括对包名进行散列,对类名进行散列,然后追加一个点和一个标识索引的连续整数后缀。这些全局名称对用户来说不太容易理解,但往往更短、效率更高。 整数后缀仅作为索引名的关键字;与索引名和索引类型相关联的字段对整数编号没有影响。例如,`^EW3K.CgZk.1` 是 `Master Map`,`^EW3K.CgZk.2`是位图范围(`Bitmap Extent`),`^EW3K.CgZk.3`是`LastName`字段的已定义标准索引`NameIDX`,`^EW3K.CgZk.4`是已定义索引`WorkIdIDX`。如果删除`NameIDX`,则全局`^EW3K.CgZk.3`也会被删除,从而在整数序列中产生间隙。如果为`LastName`字段定义`LNameIDX`,则会创建全局`^EW3K.CgZk.5`;但是,如果稍后为`FullName`字段创建位图索引`NameIDX`,则全局索引将再次为`^EW3K.CgZk.3`。 - 如果`USEEXTENTSET=1`并且未指定`DEFAULTGLOBAL`,则包名和类名将被散列,如上所述。将追加连续的整数后缀。 - 如果`USEEXTENTSET=1`并指定了`DEFAULTGLOBAL`,则使用`DEFAULTGLOBAL`名称,而不是散列的包名和类名。将追加连续的整数后缀。例如,如果`DEFAULTGLOBAL="MyGlobal"`,则全局变量的名称如下:`^MyGlobal.1`和`^MyGlobal.3` 如果使用`CREATE TABLE`命令定义表,则`USEEXTENTSET`默认为1。因此,默认情况下,`CREATE TABLE`创建散列全局名称。可以使用`%CLASSPARAMETER`关键字以及`USEEXTENTSET`和`DEFAULTGLOBAL`参数更改此默认行为。可以使用`$SYSTEM.SQL.Util.SetOption()`方法在系统范围内更改此默认设置。 ```java SET status=$SYSTEM.SQL.Util.SetOption("DDLUseExtentSet",0,.oldval). ``` 如果定义投影到表的持久类,则`USEEXTENTSET`默认为0。因此,默认情况下,使用传统的全局名称。 `DEFAULTGLOBAL`(如果已定义)将作为默认值。如果定义了`ExtentLocation`、`DataLocation`或`IndexLocation`存储关键字,则使用这些值,而不是上述默认值。 可以向`ZWRITE`提供全局名称以显示索引数据。 ## Master Map 系统自动为每个表定义一个主图(`Data/Master`)。M`aster Map`不是索引,它是使用其`Map`下标字段直接访问数据本身的`Map`。默认情况下,`Master Map`下标字段是系统定义的`RowID`字段。默认情况下,使用`RowID`字段进行的这种直接数据访问由SQL映射名称(SQL索引名称)`IDKEY`表示。 默认情况下,用户定义的主键不是`IDKEY`。**这是因为使用`RowID`整数查找 `Master Map`总是比使用主键值查找效率更高。** 但是,如果指定主键为`IDKEY`,则主键索引被定义为表的主映射,SQL映射名称为主键SQL索引名。 对于单字段`key/IDKEY,`,主键索引是主映射,但主映射数据访问列仍然是`RowID`。这是因为在记录的唯一主键字段值和其`RowID`值之间存在一对一的匹配,而`RowID`被认为是更高效的查找。对于多字段主键/`IDKEY`,会为`Master Map`指定主键索引名称,并且`Master Map Data Access`列是主键字段。 可以通过Management Portal SQL Catalog Details(管理门户SQL目录详细信息)选项卡查看主图定义。除其他项目外,它还显示存储Master Map数据的全局名称。对于SQL和默认存储,此主映射全局默认为`^Package.classnameD`,并记录命名空间以防止歧义。对于自定义存储,未定义主地图数据存储全局名称;可以使用`DATALOCATIONGLOBAL`类参数指定数据存储全局名称。 对于SQL和默认存储,主映射数据存储在下标全局名为`^package.classnameD` 或 `^hashpackage.hashclass.1`。请注意,全局名指定持久类名,而不是相应的SQL表名,并且全局名区分大小写。可以向`ZWRITE`提供全局名称以显示`Master Map`数据。 **使用`Master Map`访问数据效率很低,尤其是对于大型表。因此,建议用户定义可用于访问`WHERE`条件、联接操作和其他操作中指定的数据字段的索引。** # 自动定义的索引 定义表时,系统会自动定义某些索引。在为表格定义并在添加或修改表数据时,自动生成以下索引。如果定义: - 不是`IDKEY`的主键,则系统会生成唯一类型的相应索引。主键索引的名称可以是用户指定的,也可以是从表名派生的。例如,如果定义一个未命名的主键,则相应的索引将命名为`tablenamePKEY#`,其中`#`是每个`UNIQUE`和`PRIMARY KEY`约束的顺序整数。 - 唯一的字段,Intersystems Iris为每个唯一字段生成索引,其中名称T`ableNameUnique#`,其中`#`是每个唯一和主键约束的顺序整数。 - 唯一约束,系统为每个具有指定名称的唯一约束生成索引,为共同定义唯一值的字段编制索引。 - shard key,系统在shard key字段上生成一个索引,命名为`ShardKey`。 可以通过Management Portal SQL Catalog Details选项卡查看这些索引。`CREATE INDEX`命令可用于添加唯一字段约束;D`ROP INDEX`命令可用于删除唯一字段约束。 默认情况下,系统在`RowID`字段上生成`IDKEY`索引。定义身份字段不会生成索引。但是,如果定义标识字段并将该字段作为主键,则InterSystems IRIS将在标识字段上定义`IDKEY`索引并将其作为主键索引。下面的示例显示了这一点: ```sql CREATE TABLE Sample.MyStudents ( FirstName VARCHAR(12), LastName VARCHAR(12), StudentID IDENTITY, CONSTRAINT StudentPK PRIMARY KEY (StudentID) ) ``` 同样,如果定义标识字段并为该字段提供唯一约束,则InterSystems IRIS将在标识字段上显式定义`IdKey/Unique`索引。下面的示例显示了这一点: ```sql CREATE TABLE Sample.MyStudents ( FirstName VARCHAR(12), LastName VARCHAR(12), StudentID IDENTITY, CONSTRAINT StudentU UNIQUE (StudentID) ) ``` 这些标识索引操作仅在没有明确定义的`idkey`索引时出现,并且表不包含任何数据。 ## 位图范围索引 **位图范围索引是表的行的位图索引,而不是针对表的任何指定字段。在位图范围索引中,每个位表示顺序`ROWID`整数值,并且每个位的值指定相应的行是否存在。** **SQL使用此索引来提高`Count(*)`的性能,返回表中的记录数(行)。** **一个表最多可以有一个位图区段索引。创建多个位图范围索引导致`SQLCode -400`错误。** 其中 `ERROR #5445: Multiple Extent indices defined:DDLBEIndex.` 所有使用`CREATE TABLE`定义的表都会自动定义位图区段索引。 将此自动生成的索引分配索引名称(索引属性名称)`DDLBEIndex`和SQL MapName (SQL索引名称)`%%DDLBEIndex`。 定义为类的表可以有一个位图区索引,索引名和`$ClassName`的SQL MapName(其中`ClassName`是表的持久化类的名称)。 可以使用带有`BITMAPEXTENT`关键字的`CREATE INDEX`命令将位图区段索引添加到表中,或者重命名自动生成的位图区段索引。 可以通过管理门户SQL Catalog详细选项卡查看表的位图范围索引。虽然表只有一个位图范围索引,但是从另一个表中继承的表在其自身位图范围索引和它从其扩展的表中的位图范围索引中列出。例如,`Sample.employee`表扩展了`Sample.person`表;在目录详细信息映射 `Sample.Employee`列出`$Employee` 和 `$Person Bitmap`范围索引。 在经历许多删除操作的表格中,位图范围索引的存储可以逐渐变得效率较低。可以通过选择表的“目录详细信息”选项卡,“映射”选项和选择重建索引来重建从管理门户中重建位图范围索引。 ![image](/sites/default/files/inline/images/1_33.png) **%SYS.Maint.Bitmap实用程序方法压缩位图范围索引,以及位图指数和bitslice索**引。 在以下任何情况下,调用`%BuildIndices()`方法都会构建现有的位图范围索引:未指定`%BuildIndices() pIndexList`参数(构建所有定义的索引);`pIndexList`按名称指定位图范围索引;或`pIndexList`指定任何定义的位图索引。 # 定义索引 ## 使用类定义定义索引 在Studio中,可以使用新建索引向导或通过编辑类定义的文本将索引定义添加到`%Persistent`类定义。索引在一个或多个索引属性表达式上定义,后跟一个或多个可选索引关键字(可选)。它采用以下形式: ```sql INDEX index_name ON index_property_expression_list [index_keyword_list]; ``` - `index_name`是有效的标识符。 - `index_property_expression_list`是一个或多个以逗号分隔的属性表达式的列表,它们作为索引的基础。 - `index_keyword_list`是一个可选的索引关键字列表,用逗号分隔,用方括号括起来。 用于指定位图或位片索引的索引类型。 也用于指定唯一的、`IdKey`或`PrimaryKey`索引。 (根据定义,`IdKey`或`PrimaryKey`索引也是唯一索引。) 索引关键字的完整列表出现在类定义引用中。 `index_property_expression_list`参数由一个或多个索引属性表达式组成。 索引属性表达式包括: - 要建立索引的属性的名称。 - 可选(元素)或(键)表达式,提供对集合子值进行索引的方法。 如果`index`属性不是一个集合,用户可以使用`BuildValueArray()`方法生成一个包含键和元素的数组。 - 可选的排序规则表达式。 它包含一个排序规则名称,后面可选地跟着一个或多个以逗号分隔的排序规则参数列表。 不能为惟一索引、`IdKey`索引或`PrimaryKey`索引指定索引排序规则。 唯一索引或`PrimaryKey`索引从正在建立索引的属性(字段)中获取其排序规则。 `IdKey`索引总是精确(`EXACT`)的排序。 例如,下面的类定义定义了两个属性和一个基于它们的索引: ```java Class MyApp.Student Extends %Persistent [DdlAllowed] { Property Name As %String; Property GPA As %Decimal; Index NameIDX On Name; Index GPAIDX On GPA; } ``` 更复杂的索引定义是: ```sql Index Index1 On (Property1 As SQLUPPER(77), Property2 AS EXACT); ``` ### 可以建立索引的属性 唯一可以被索引的属性是: - 那些存储在数据库中的 - 那些可以从存储的属性可靠地派生出来的 必须使用`SQLComputed`关键字定义可以可靠地派生(并且未存储)的属性; `SQLComputeCode`指定的代码必须是导出属性值的唯一方法,并且无法直接设置属性。 如果可以直接设置一个派生属性的值,比如是一个简单的情况下(non-collection)属性定义为瞬态和不也定义为计算,然后直接设置属性的值将覆盖`SQLComputeCode`中定义的计算和存储的值不能可靠地来自属性; 这种类型的派生属性称为不确定性。(计算的关键字实际上意味着没有分配实例内存。) 一般规则是,只有定义为`calculate`和`SQLComputed`的派生属性才能被索引。 但是,派生集合有一个例外:派生的(`SQLComputed`)集合是暂时的(没有存储)集合,也没有定义为计算的集合(意味着没有实例内存)可以被索引。 **注意:`IdKey`索引所使用的任何属性的值内都不能有连续的一对竖条(`||`),除非该属性是对持久类实例的有效引用。** 这个限制是InterSystems SQL内部机制所要求的。 在`IdKey`属性中使用`||`会导致不可预知的行为。 ### 多个属性的索引 可以在两个或多个属性(字段)的组合上定义索引。在类定义中,使用索引定义的`ON`子句指定属性列表,例如: ```java Class MyApp.Employee Extends %Persistent [DdlAllowed] { Property Name As %String; Property Salary As %Integer; Property State As %String(MAXLEN=2); Index MainIDX On(State,Salary); } ``` 如果需要执行使用字段值组合的查询,例如: ```sql SELECT Name,State,Salary FROM Employee ORDER BY State,Salary ``` ### 索引排序 唯一索引、`PrimaryKey`索引或`IdKey`索引不能指定排序规则类型。 对于其他类型的索引,索引定义中指定的每个属性都可以有一个排序规则类型。 应用索引时,索引排序类型应与属性(字段)排序类型匹配。 1. 如果索引定义包含为属性显式指定的排序规则,则索引使用该排序规则。 2. 如果索引定义不包括为属性显式指定的排序规则,则索引使用属性定义中显式指定的排序规则。 3. 如果属性定义不包括显式指定的排序规则,则索引使用属性数据类型的默认排序规则。 例如,`Name`属性被定义为字符串,**因此在默认情况下具有`SQLUPPER`排序规则。** 如果在`Name`上定义一个索引,默认情况下,它接受属性的排序规则,索引也将使用`SQLUPPER`定义。 属性排序和索引排序匹配。 但是,如果比较应用不同的排序规则,例如,`WHERE %EXACT(Name)=%EXACT(:invar)`,则此用法中的属性排序规则类型不再与索引排序规则类型匹配。属性比较排序规则类型与索引排序规则类型之间的不匹配可能会导致不使用索引。因此,在这种情况下,可能希望为具有精确(`EXACT`)排序规则的`Name`属性定义索引。如果`JOIN`语句的`ON`子句指定了排序规则类型,例如,`FROM Table1 LEFT JOIN Table2 ON %EXACT(Table1.Name) = %EXACT(Table2.Name)`,此处指定的属性排序类型与索引排序类型不匹配可能导致InterSystems IRIS不使用该索引。 以下规则控制索引和属性之间的排序规则匹配: - **匹配的排序规则类型总是最大限度地使用索引。** - **排序规则类型不匹配,其中属性指定为精确的排序规则(如上所示),并且索引有一些其他的排序规则,允许使用索引,但是它的使用不如匹配排序类型有效。** - **排序规则类型不匹配,其中属性排序规则不准确,属性排序规则不匹配索引排序规则,这将导致不使用索引。** 要在索引定义中显式地为属性指定排序规则,语法如下: ```sql Index IndexName On PropertyName As CollationName; ``` - `IndexName`是索引的名称 - `PropertyName`是被索引的属性 - `CollationName`是用于索引的排序规则的类型 例如: ```sql Index NameIDX On Name As Exact; ``` 不同的属性可以有不同的排序规则类型。 例如,在下面的例子中,`F1`属性使用`SQLUPPER`排序,而F2使用`EXACT`排序: ```sql Index Index1 On (F1 As SQLUPPER, F2 As EXACT); ``` **注意:指定为`Unique`、`PrimaryKey`或`IdKey`的索引不能指定索引排序规则。 索引从属性`collations`中获取其`collation`。**
文章
Nicky Zhu · 一月 11, 2021

类、表和Globals——工作原理

当我向技术人员介绍InterSystems IRIS时,我一般会先讲其核心是一个多模型DBMS。 我认为这是其主要优势(在DBMS方面)。数据仅存储一次。您只需访问您想用的API。 - 您想要数据的概要?用SQL! - 您想用一份记录做更多事情?用对象! - 想要访问或设置一个值,并且您知道键?用Globals! 乍一看挺好的,简明扼要,又传达了信息,但当人们真正开始使用InterSystems IRIS时,问题就来了。类、表和Globals是如何关联的?它们之间有什么关系?数据是如何存储的? 本文我将尝试回答这些问题,并解释这些到底是怎么回事。 ## 第一部分 模型偏见 处理数据的人往往对他们使用的模型有偏见。 开发者们把数据视为对象。对他们而言,数据库和表都是通过CRUD(增查改删,最好是基于ORM)交互的盒子,但底层的概念模型都是对象(当然这对于我们大多数使用面向对象编程语言的开发者来说没错)。 而DBA大部分时间都在搞关系型DBMS,他们把数据视为表。对象只是行的封装器。 对于InterSystems IRIS,持久类也是一个表,将数据存储在Global中,因此需要进行一些澄清。 ## 第二部分 举例 假设您创建了类Point: ```objectscript Class try.Point Extends %Persistent [DDLAllowed] { Property X; Property Y; } ``` 您也可以用DDL/SQL创建相同的类: ``` CREATE Table try.Point ( X VARCHAR(50), Y VARCHAR(50)) ``` 编译后,新类将自动生成一个存储结构,将原生存储在Global中的数据映射到列(对于面向对象开发者而言,是属性): ``` Storage Default { %%CLASSNAME X Y ^try.PointD PointDefaultData ^try.PointD ^try.PointI ^try.PointS %Library.CacheStorage } ``` 这是怎么回事? 自下向上(加粗文字很重要): - Type: 生成的存储类型,本例中是持久对象的默认存储 - StreamLocation - 存储流的Global - IndexLocation - 索引Global - IdLocation - 存储ID自增计数器的Global - **DefaultData** - 存储将Global值映射到列/属性的XML元素 - **DataLocation** - 存储数据的Global 现在我们的DefaultData是PointDefaultData,让我们分析下它的结构。本质上Global节点有这样的结构: - 1 - %%CLASSNAME - 2 - X - 3 - Y 所以我们可能期望我们的Global是这样的: ``` ^try.PointD(id) = %%CLASSNAME, X, Y ``` 但如果我们输出 Global 它会是空的,因为我们没有添加任何数据: ``` zw ^try.PointD ``` 让我们添加一个对象: ``` set p = ##class(try.Point).%New() set p.X = 1 set p.Y = 2 write p.%Save() ``` 现在我们的Global变成了这样 ``` zw ^try.PointD ^try.PointD=1 ^try.PointD(1)=$lb("",1,2) ``` 可以看到,我们期望的结构%%CLASSNAME, X, Y是用 $lb("",1,2) 设置的,它对应的是对象的X和Y属性(%%CLASSNAME 是系统属性,忽略)。 我们也可以用SQL添加一行: ``` INSERT INTO try.Point (X, Y) VALUES (3,4) ``` 现在我们的Global变成了这样: ``` zw ^try.PointD ^try.PointD=2 ^try.PointD(1)=$lb("",1,2) ^try.PointD(2)=$lb("",3,4) ``` 所以我们通过对象或SQL添加的数据根据存储定义被存储在Global中(备注:可以通过在PointDefaultData 中替换X和Y来手动修改存储定义,看看新数据会怎样!)。 现在,如果我们想执行SQL查询会怎样? ``` SELECT * FROM try.Point ``` 这段sql查询被转换为ObjectScript代码, 遍历^try.PointD,并根据存储定义(其中的 PointDefaultData 部分)填充列。 下面是修改。让我们从表中删除所有数据: ``` DELETE FROM try.Point ``` 看看我们的Global变成什么样了: ``` zw ^try.PointD ^try.PointD=2 ``` 可以看到,只剩下ID计数器,所以新对象/行的ID=3。我们的类和表也继续存在。 但如果我们运行会怎样: ``` DROP TABLE try.Point ``` 它会销毁表、类并删除Global。 ``` zw ^try.PointD ``` 看完这个例子,希望您现在对Global、类和表如何相互集成和互补有了更好的理解。根据实际需要选用正确的API会让开发更快、更敏捷、bug更少。
文章
姚 鑫 · 七月 17, 2021

第六章 使用%File对象

# 第六章 使用%File对象 如果想要操作文件本身,需要使用`%Library.File`的`%New()`方法实例化`%File`对象。该类还提供了允许使用该文件的实例方法。 注意:本节提供了几个使用`%File`对象的示例,以供说明。 对于简单的文件读写,使用`%Stream.FileCharacter`和%`Stream.FileBinary`。因为它们提供了额外的功能,例如,以正确的模式自动打开文件。 ## 创建%File对象的实例 要使用文件,需要使用`%New()`方法实例化表示该文件的%File对象。该文件可能已经存在,也可能不存在于磁盘上。 以下示例在默认目录中为文件`export.xml`实例化一个%File对象。 ```java set fileObj = ##class(%File).%New("export.xml") ``` ## 打开和关闭文件 实例化`%File`对象后,需要使用`open()`方法打开文件,以读取或写入该文件: ```java USER>set status = fileObj.Open() USER>write status 1 ``` 使用`Close()`方法关闭文件: ```java USER>do fileObj.Close() ``` ## 检查%File对象的属性 一旦实例化了文件,就可以直接检查文件的属性。 ```java USER>write fileObj.Name export.xml USER>write fileObj.Size 2512 USER>write $zdate(fileObj.DateCreated) 11/18/2020 USER>write $zdate(fileObj.DateModified) 11/18/2020 USER>write fileObj.LastModified 2020-11-18 14:24:38 USER>write fileObj.IsOpen 0 ``` 请注意,`LastModified`是人类可读的时间戳,而不是`$H`格式的日期。 属性“大小Size”、“创建日期DateCreated”、“修改日期DateModified”和“最后修改日期LastModified”是在访问时计算的。为不存在的文件访问这些属性会返回-2,表示找不到该文件。 注意:Windows是目前唯一跟踪实际创建日期的平台。其他平台存储最后一次文件状态更改的日期。 ```java USER>write ##class(%File).Exists("foo.xml") 0 USER>set fooObj = ##class(%File).%New("foo.xml") USER>write fooObj.Size -2 ``` 如果文件已打开,可以通过访问`CanonicalName`属性来查看其规范名称,这是根目录的完整路径。 ```java USER>write fileObj.CanonicalName USER>set status = fileObj.Open() USER>write fileObj.IsOpen 1 USER>write fileObj.CanonicalName c:\intersystems\IRIS\mgr\user\export.xml ``` ## 从文件中读取 要读取文件,可以打开文件,然后使用`Read()`方法。 以下示例读取`messages.log`的前`200`个字符。 ```java USER>set messages = ##class(%File).%New(##class(%File).ManagerDirectory() _ "messages.log") USER>set status = messages.Open("RU") USER>write status 1 USER>set text = messages.Read(200, .sc) USER>write text *** Recovery started at Mon Dec 09 16:42:01 2019 Current default directory: c:\intersystems\IRIS\mgr Log file directory: .\ WIJ file spec: c:\intersystems\IRIS\mgr\IR USER>write sc 1 USER>do messages.Close() ``` 要从文件中读取整行,请使用`ReadLine()`方法,该方法继承自`%Library.File`的父类`%Library.AbstractStream`。 下面的示例读取`E:\temp\new.txt`的第一行。 ```java /// desc: 读取数据 /// w ##class(Demo.FileDemo).ReadFileData("E:\temp\new.txt") ClassMethod ReadFileData(str) { s fileObj = ##class(%File).%New(str) s status = fileObj.Open("RU") w status,! s text = fileObj.ReadLine(,.sc) w text,! w sc,! d fileObj.Close() q "" } ``` ## 写入文件 要写入文件,可以打开文件,然后使用`Write()`或`WriteLine()`方法。 以下示例将一行文本写入新文件。 ```java /// desc: 写入数据 /// w ##class(Demo.FileDemo).WriteFileData("E:\temp\new.txt") ClassMethod WriteFileData(str) { s fileObj = ##class(%File).%New(str) s status = fileObj.Open("RUWSN") w status,! s status = fileObj.WriteLine("Writing to a new file.") w status,! w fileObj.Size,! d fileObj.Rewind() s text = fileObj.ReadLine(,.sc) w text,! q "" } ``` ## 倒回文件 从文件读取或写入文件后,希望使用`Rewind()`方法倒回文件,以便可以从文件开头执行操作。 从上一个示例停止的地方开始,`fileObj`现在位于其末尾。倒回文件并再次使用`WriteLine()`会覆盖该文件。 ```java USER>set status = fileObj.Rewind() USER>write status 1 USER>set status = fileObj.WriteLine("Rewriting the file from the beginning.") USER>write status 1 USER>write fileObj.Size 40 ``` 关闭文件并重新打开它也会倒回文件。 ```java USER>do fileObj.Close() USER>set status = fileObj.Open("RU") USER>write status 1 USER>set text = fileObj.ReadLine(,.sc) USER>write sc 1 USER>write text Rewriting the file from the beginning. ``` ## 清除文件 要清除文件,可以打开文件,然后使用`Clear()`方法。这将从文件系统中删除该文件。 以下示例清除默认目录中的`junk.xml`。 ```java USER>write ##class(%File).Exists("junk.xml") 1 USER>set fileObj = ##class(%File).%New("junk.xml") USER>set status = fileObj.Open() USER>write status 1 USER>set status = fileObj.Clear() USER>write status 1 USER>write ##class(%File).Exists("junk.xml") 0 ```
问题
kun an · 五月 27, 2021

按照官方教程Java QuickStart章节中下载的示例程序 xep方式访问不了

按照此网址的指示 https://gettingstarted.intersystems.com/language-quickstarts/java-quickstart/ 下载quickstarts-java示例代码 运行xepplaystocktsTask1模块程序提示InterSystems XEP is not supported by the specified server 请问有人知道具体原因吗我的server有什么不对吗。 使用客户端的server manager添加server没有server类型的选项,是因为我使用的cache版本目前不支持吗 不知您测试使用的具体是IRIS哪个版本,以及运行在哪个操作系统上。 还有提到的客户端使用的是哪个IRIS版本。低版本的客户端没法连上高版本的IRIS服务端,Caché客户端更加连不上IRIS的服务器。 您好 感谢解答。我使用的是IRISHealth_Community-2020.1.0.217.1-win_x64 运行在windows10上。我在官网教程上下载的示例代码,其余访问方式都运行正常就xep方式报错, 提示指定的server不支持, 不知道都有什么类型的server,哪些server可以支持 请参考下面资料检查是否符合运行需求以及相关配置是否正确:https://docs.intersystems.com/irisforhealth20201/csp/docbook/Doc.View.cls?KEY=BJAVXEP_intro#BJAVXEP_intro_config 其中的检查包括JDK版本、安装时的选项、服务%Service_CallIn是否打开以及环境变量CLASSPATH 的设置
文章
Michael Lei · 十二月 30, 2021

翻译--在ECP架构下部署多个IRIS 实例 - 例子

对于那些在某种程度上需要测试ECP的水平可扩展性(计算能力和/或用户和进程的并发性),但又懒得建立环境、配置服务器节点等的人来说,我刚刚在Open Exchange上发布了OPNEx-ECP部署的应用/示例。 这只是一个小项目,可以在GitHub上找到,供所有人使用。基本上,它可以让你在自己的笔记本上建立3个InterSystems IRIS实例作为应用服务器,1个实例作为数据服务器,通过ECP连接。 它还将启动一个LoadBalancer(使用WebGateway),作为一个独立于其他实例的节点,它将是进入你的系统的入口(通过HTTP),它将负责在应用服务器之间分配请求。 只需很少的工作,你就可以添加你的应用代码,并利用该项目在分布式ECP中测试你的应用。应用服务器预装了其他有趣的开源模块(ZPM, WebTerminal, RestForms2 y Restforms2-ui),还有一个小包OPNEx-Model,其中有一些类和一个REST服务作为例子来实现。 详细信息请访问Open Exchange或GitHub。 好了,希望这对你有帮助! 祝大家编程愉快!!
公告
Claire Zheng · 十一月 9, 2023

来投票选出适合发布公告的创意吧!

Hi 社区成员们! 我们非常兴奋地推出一系列全新的公告,展示您在创意门户中所提交的创意的实施情况。通过这个系列,我们将聚焦那些已转化为现实世界解决方案的令人赞叹的想法。 我们的社区一直是创造力、创新和协作的重要中心,这正说明了我们拥有一批非凡的人才。通过这些公告,我们为那些从奇思妙想到代码诞生而庆祝欢呼。本系列中的每个公告都将展示 InterSystems 产品概念的建议想法,并让您亲身体验代码的神奇魅力。 第一批得到实施的创意是: 创意 创意实施者 Global->JSON->Global converter @Robert Cemper Introduce the project of helpful one-liners @Evgeny Shvarov Examples to work with IRIS from Django @Dmitry Maslennikov @Heloisa Paiva 3DES support @yurimarx Marx Include support for gRPC protocol in IRIS @Guillaume Rongier Create a UI for convenient and easy transfer of projects to other system instances for fast deployment. @Sergey Mikhailenko 在下面的投票中投票选出您希望首先展示的想法。 一如既往地欢迎您在评论中提出有关公告内容的建议!
文章
Lilian Huang · 七月 20, 2022

FHIR 中的问卷和表格(Questionnaire & Forms):从创建到使用

本文将讨论 FHIR 中的问卷和问卷反馈(Questionnaire and Questionnaire Response), 从创建表单到上传到服务器以及如何填写它们。 tl;dr : 通过使用该工具链接“ this online tool” ,您可以轻松的开始构建您自己的表单,或者使用现有模版。 通过使用InterSystems 本地FHIR 服务器链接“ this InterSystems local FHIR server” ,您可以轻松的存储您的FHIR资源和问卷。 通过使用此应用程序“this app” ,您可以像医生一样操作,对您的 FHIR 服务器上的每位患者进行问卷调查和回复。 需要注意的是,该应用程序不使用 Content-Type 'application/json+fhir' 进行通信,而只是使用 Content-Type 'application/json' ,所以它不会像我们的本地 InterSystems FHIR 服务器那样工作。 这就是为什么我创建了这个 GitHub 存储库“this GitHub repo”,其中包含应用程序的修改版本,使用 Content-Type 'application/json+fhir',拥有本地 FHIR 服务器和指向问卷生成器工具的链接以及一些解释。 克隆 repo 后,通过执行 docker-compose up -d, npm ci, npm run build 然后出现 npm run start ,您将可以访问该应用程序,通过选择您想要的 FHIR 服务器和您想要工作的患者使用,您将能够填写调查问卷并将其保存到您的服务器中,只需单击 2 次。 End of tl;dr 接下来是 GitHub 的自述文件。 1. 使用本地 fhir 服务器的 FHIR 表单应用程序包括如下 1. App for FHIR forms using a local fhir server(使用本地 fhir 服务器的 FHIR 表单应用程序) 2. Requirements(要求) 2.1. Add Node.js and npm to your path(将 Node.js 和 npm 添加到您的路径) 2.2. Install Dependencies(安装依赖项) 3. Local FHIR server(本地FHIR服务器) 4. Using the app(使用应用程序) 4.1. Build the application(构建应用程序) 4.2. Run the Application(运行应用程序) 5. FHIR form / questionnaire (FHIR表格/问卷) 5.1. Creating your own FHIR form(创建您自己的 FHIR 表单) 5.2. Importing your FHIR form(导入您的 FHIR 表格) 这是一个基于此“this repo”的应用程序,可用于显示, “FHIR” “SDC” “Questionnaire”并收集数据作为 FHIR 问卷反馈的资源。 通过使用docker-compose up -d ,您将可以访问本地 FHIR 服务器“local FHIR server” 然后可以使用该服务器来测试应用程序。 2. 要求 该应用程序依赖于“LHC-Forms “ 渲染,用于显示表单的小部件,它支持部分 FHIR 问卷(版本 STU3 和 R4) 和结构化数据捕获实现指导“Structured Data Capture ImplementationGuide“。此小部件将与依赖项一起安装。 为可以尝试一些示例表单,这个库里在这个下面e2e-test/data/附带了一些表单, 会在构建时自动加载到本地 FHIR 服务器。 2.1. 将 Node.js 和 npm 添加到您的路径 文件 bashrc.lforms-fhir-app 指定了我们正在为开发使用的 Node.js 的版本,下载Node.js的该版本,并将其 bin 目录添加到你的路径。 2.2. 安装依赖项 通过运行此命令,您将能够安装应用程序运行所需的一切。 npm ci 3. 本地FHIR 服务器 如果您没有 FHIR 服务器来试用此应用程序,您可以在 fhir-form 文件夹中启动并使用由 InterSystems 技术支持的本地 FHIR 服务器: docker-compose up -d 等待一段时间后,您的本地 FHIR 服务器已启动,您可以使用 http://localhost:32783/fhir/r4请注意,此链接已在应用程序中注册。 4. 使用应用程序 要使用该应用程序,您必须构建“build” 并启动“start” 它。您现在可以使用应用程序的菜单访问您选择的任何 FHIR 服务器,但如果您愿意,您可以使用此本地 FHIR 服务器“ local FHIR server”。 4.1. 构建应用程序 npm run build 这将在“dist”目录中创建用于生产的文件,但也会从node_modules 复制一些需要的文件中。 4.2.运行应用程序 npm run start 将启动一个在 8000 端口运行的 http 服务器。 现在在localhost:8000/lforms-fhir-app/浏览到应用程序。 在这里您可以选择要连接的服务器。 如果要使用本地 FHIR 服务器,请启动本地 FHIR 服务器“start the local FHIR server”, 然后在应用程序上选择第一个选项 http://localhost:32783/fhir/r4 5. FHIR表格/问卷 5.1. 创建您自己的 FHIR 表单 通过使用这个在线工具“this online tool”, 您可以轻松地从头开始构建自己的表单或使用现有的表单。 我们建议您导入 e2e-tests/data/R4 文件夹中现有的一个,然后从这里开始了解该工具的工作原理。 5.2. 导入您的FHIR表格 使用该应用程序,您可以轻松导入本地表单并使用上传按钮upload 立即使用它们。 如果您使用的是工具”formbuilder tool “, 如果您有支持Content-Type 'application/json' 的 FHIR 服务器,则可以使用导出按钮将您正在创建的表单直接导出到 fhir 服务器。 如果您的服务器不支持 Content-Type 'application/json' 而只支持 Content-Type 'application/json+fhir' 例如,作为我们的本地 FHIR 服务器“local FHIR server “,您必须将表单导出到文件,然后在应用程序上 ,将文件上传到服务器,因为应用程序以 Content-Type 'application/json+fhir' 进行通信。 原文请点击该链接:https://community.intersystems.com/post/questionnaire-forms-fhir-creation-usage#3-local-fhir-server
文章
Hao Ma · 一月 4, 2023

IRIS, Caché监控指导 - 系统健康检查

以下是我们应客户的要求拟定的Caché系统健康检查的建议。InterSystems的工程师们认为其中的项目足以了解客户当前的系统健康状况。 这些项目中有些,比如Buttons, pButtons报告是必须的,其他内容,尤其是问卷部分,越多回答对系统健康的了解也越清楚。InterSystems公司的技术支持中心WRC(World Response Center),在合适的条件下可以协助用户解读健康检查的结果。 在后面的内容中, 我会详细介绍这些检查的项目,比如报告的执行步骤,已经如何简单的发现问题。 检查的内容也适用于IRIS,仅仅是执行的步骤上有细微的区别,后面文章会详细说。 ## 健康检查项目 本健康检查只用于Caché系统本身的内容, 不包括Caché上使用的各种应用。 建议用户收集下列两部分数据和资料: ### 系统运行数据 - [ ] 所有Caché实例服务器的网络架构图,包含所有的数据服务器,应用服务器,镜像服务器,灾备服务器。还应该包含网段的划分, 相关的Web服务器,负载均衡设备的部署等情况。以及一切客户认为和Caché工作相关的网络配置的情况。 - [ ] Caché数据库使用的存储设备的信息, 不限于类型,大小,品牌等等任何可以帮助了解存储设备的信息。 - [ ] 所有数据库上一次的完整性检查报告。 - [ ] 所有Caché实例的 - [ ] 系统监控检查报告(Buttons) - [ ] 24小时系统性能报告(pButtons): 所有关联的系统,比如一个Caché数据服务器以及和它连接的应用服务器(ECP服务器),应该在尽量相同的时间执行24小时pButton测量 - [ ] 一年内或自上次启动后(以其中更长时间为准)的Console日志 - [ ] 导出的日常任务(Task) - [ ] 导出的后台任务历史列表 - [ ] 系统时钟同步的配置 - [ ] 所有CSP Gateway的配置文件,以及CSP Gateway工作的Apache Web Server, Nginx Web Server,Windows IIS的配置文件。 - [ ] 如果用户使用了外部备份,请提供外部备份的操作步骤及使用的脚本程序。 ### 维护工作的问卷 以下问题的回答能帮助InterSystems的工程师更好的了解客户的Caché工作情况,以及更方便的分析上面采集的数据。 - [ ] 请列出近一年内Caché的软硬件变动 - [ ] 是否有测试环境(TestBed), 测试服务器的梳理,配置 - [ ] 请提供Caché的日常维护的情况说明,尽可能提供以下日常维护的方案,执行频率,执行时长等等。包括但不限于: - [ ] 备份恢复 - 方案,Caché在线备份还是外部备份。如果是Caché在线备份,各种备份类型的安排情况(全备份,增量备份,累计备份) - 执行频率,执行的时间点 - 各种数据量情况下的执行时长,不如全备份的时长,增量备份的数据量是多少,执行时长是多少等等 - [ ] 数据库完整性检查 - 完整性检查的方案,频率 - 数据库的大小及对应的完整性检查的执行时长 - [ ] 告警通知 - 告警通知发送的方式。(告警通知默认是Console log里严重级别为2,3的条目) - 告警通知的处理流程 - 告警通知的产生:是否有客户定制的通知消息 - Console Log中出现的严重级别为1的消息(Warning消息)是否被通知,或者是否有任何处理方式 - [ ] 性能测量 - 提供业务活动量在一段时间内的变动模式, 比如一周,一天中业务量的忙时,闲时,以及是否月初活着月底有大的报表生成等等 - 详细列出各种周期性执行的和Caché性能相关的操作的时间点和时长,处了上面提到的备份恢复,数据库完整性检测等,还可以是任意的Caché操作,以及Caché所在的虚拟机,服务器的操作,还可以包括可能影响Caché性能表现的连接的第3方的业务系统监控系统,审计系统的与之有关的操作 - 是否有常规的性能测试方案,包括Caché上的指标测量(pButtons), 以及操作系统的性能指标测量 - 无论以何种形式,是否能提供Caché系统的性能基准。这个性能基准应该以客户的业务活动量做为采样周期,比如以周为单位 - 上述指标是否能提供图表的展示 - [ ] 尽可能的提供近一年中在Caché日常维护中遇到的各种故障及异常的列表。对列表中的每一项,尽量提供详细的描述和信息,包括并不限于: - 是否报告InterSystems, 如果报告了, WRC号码是多少 - 发生的频率如何? - 如果已经有解决,解决的方案是什么? - 如果没有经过人工处理,那么故障恢复的时长平均是多少? - 维护工程师对故障产生的原因以及造成后果的分析讨论的结果,如果有。 - [ ] 其他内容(可选) - Caché维护团队的工作分配, 以及相关的外部团队的职责,比如应用实施方,用户的其他IT团队,硬件维护,硬件监控团队等等。 - 对Caché维护最期待的改进,工具的提供等 - 其他任何有关Caché维护工作而上面各项中未涵盖的内容。
文章
姚 鑫 · 二月 21, 2021

第四十三章 Caché 变量大全 $ZTIMEZONE 变量

# 第四十三章 Caché 变量大全 $ZTIMEZONE 变量 包含格林威治子午线的时区偏移量。 # 大纲 ``` $ZTIMEZONE $ZTZ ``` # 描述 `$ZTIMEZONE`可以通过两种方式使用: - 返回计算机的本地时区偏移量。 - 为当前进程设置本地时区偏移量。 `$ZTIMEZONE`包含从格林威治子午线偏移的时区(以分钟为单位)。 (格林威治子午线包括整个英国和爱尔兰。)此偏移量表示为-1440到1440范围内的有符号整数。格林威治以西的时区指定为正数;格林威治东部的时区指定为负数。 (时区必须以分钟为单位,因为并非所有时区都以小时为单位。)默认情况下,`$ZTIMEZONE`初始化为计算机操作系统设置的时区。 注意:`$ZTIMEZONE`将本地时间调整为固定的偏移量。它不适应夏令时或其他当地时间的变化。 InterSystems IRIS从基础操作系统获取本地时间,该操作系统将本地时间变体应用于为该计算机配置的位置。因此,使用`$ZTIMEZONE`调整的本地时间将从配置的语言环境中获取其本地时间变化,而不是在`$ZTIMEZONE`中指定的时区。 使用格林威治子午线(`$ZTIMEZONE = 0`)的时区计数来计算UTC时间。它与当地格林威治时间不同。格林威治标准时间(GMT)一词可能令人困惑;格林威治的当地时间与冬季的UTC相同。在夏季,它与UTC的差异为一小时。这是因为应用了称为英国夏令时的本地时间变体。 对于使用`$ZTIMEZONE`的函数和程序,经过的本地时间始终是连续的,但是时间值可能需要季节性调整以与本地时钟时间相对应。 ## 设定时区 可以使用`$ZTIMEZONE`设置当前InterSystems IRIS进程使用的时区。设置`$ZTIMEZONE`不会更改默认的InterSystems IRIS时区或计算机的时区设置。 注意:更改`$ZTIMEZONE`特殊变量是为某些特殊情况设计的功能。更改`$ZTIMEZONE`并不是更改InterSystems IRIS用于本地日期/时间操作的时区的一致方法。除非已准备好处理所有导致的不一致的程序,否则不应更改`$ZTIMEZONE`特殊变量。 在某些平台上,更改时区可能比更改`$ZTIMEZONE`特殊变量更好。如果平台具有特定于进程的时区设置(例如POSIX系统上的TZ环境变量),则进行外部系统调用来更改特定于进程的时区可能比更改`$ZTIMEZONE`更好。在操作系统级别更改特定于流程的时区将更改UTC的本地时间偏移,并应用确定何时应用本地时变的相应算法。如果默认系统时区在北半球,而所需的过程时区在南半球,则这尤其重要。更改`$ZTIMEZONE`会将本地时间更改为与UTC偏移的新时区,但是确定何时应用本地时变的算法保持不变。 使用`SET`命令将`$ZTIMEZONE`设置为指定的带符号整数分钟数。数字的前导零和小数部分将被忽略。如果在设置`$ZTIMEZONE`时指定非数字值或无值,则InterSystems IRIS会将`$ZTIMEZONE`设置为0(格林威治子午线)。 例如,北美东部标准时间(EST)在格林威治以西五个小时。因此,要将当前的InterSystems IRIS流程设置为EST,则需要指定300分钟。要指定格林威治以东一小时的时区,请指定–60分钟。要指定格林威治本身,可以指定0分钟。 设置`$ZTIMEZONE`: - 影响无参数的`$NOW()`当地时间值。它更改了`$NOW()`的时间部分,并且此时间更改也可以更改当前进程的`$NOW()`的日期部分。 `$NOW()`精确地反映了`$ZTIMEZONE`设置,其值未针对本地时变进行调整。 - 影响`$HOROLOG`当地时间值。 `$HOROLOG`从`$ZTIMEZONE`获取其时区值,然后季节性调整本地时间,例如夏令时。因此,`$HOROLOG`始终符合本地时钟时间,但全年的`$HOROLOG`经过时间不是连续的。 - 影响`%SYSTEM.Util`类方法`IsDST()`,`UTCtoLocalWithZTIMEZONE()`和`LocalWithZTIMEZONEtoUTC()`。 - 不会影响`$ZTIMESTAMP`或`$ZHOROLOG`值。 - 不会影响`$ZDATE`,`$ZDATEH`,`$ZDATETIME`,`$ZDATETIMEH`,`$ZTIME`和`$ZTIMEH`函数执行的日期和时间格式转换。 - 不会影响`$NOW(n`)函数。 - 不会影响`%SYSTEM.Process`类的`FixedDate()`类方法,该方法将`$HOROLOG`中的日期设置为固定值。 更改`$ZTIMEZONE`后发生以下异常: - `$ZDATETIME($HOROLOG,1,7)`通常返回UTC时间,但是如果`$ZTIMEZONE`已更改,它将不返回UTC时间。 - 如果`$ZTIMEZONE`已更改,`$ZDATETIME($HOROLOG,1,5)`将不会返回正确的时区偏移量。 - 如果`$ZTIMEZONE`已更改,则本地时间和UTC时间之间的`$ZDATETIME($HOROLOG,-3)`和`$ZDATETIMEH($ZTIMESTAMP,-3)`转换将不正确。 ## 其他时区方法 可以通过调用`TimeZone()`类方法来获取相同的时区信息,如下所示: ```java DHC-APP>WRITE $SYSTEM.SYS.TimeZone() -480 ``` 可以使用tformat值为5或6的`$ZDATETIME`和`$ZDATETIMEH`函数,将本地时间变化作为日期和时间字符串的一部分返回,如以下示例所示 ```java DHC-APP>WRITE !,$ZDATETIME($HOROLOG,1,5) 02/10/2021T18:24:21+08:00 ``` 该字符串的最后一部分(+08:00)表示系统的本地时间变化设置,以格林威治子午线为单位,以小时和分钟为单位进行偏移。注意,这种变化不一定是时区偏移量。在上述情况下,时区位于格林威治(-5:00)西部5小时,但是本地时区(夏令时)将时区时间偏移一小时到-04:00。设置`$ZTIMEZONE`将更改`$ZDATETIME($HOROLOG,1,5)`返回的当前处理日期和时间,但不会更改系统本地时间变化设置。 ## `$ZDATETIMEH`使用时区设置 可以将`$ZDATETIMEH`与`dformat = -3`一起使用,以将协调世界时(UTC)日期和时间值转换为本地时间。该函数将UTC值(`$ZTIMESTAMP`)作为输入。它使用本地时区设置来返回相应的日期和时间,并在适用的情况下应用本地时变(例如夏时制)。 ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE() ClassMethod ZTIMEZONE() { SET clock=$HOROLOG SET stamp=$ZDATETIMEH($ZTIMESTAMP,-3) WRITE !,"本地/本地日期和时间: ",$ZDATETIME(clock,1,1,2) WRITE !,"UTC/本地日期和时间: ",$ZDATETIME(stamp,1,1,2) } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE() 本地/本地日期和时间: 02/10/2021 18:31:27.00 UTC/本地日期和时间: 02/10/2021 18:31:27.94 ``` ## 使用`$ZTIMEZONE`的本地/UTC转换方法 `%SYSTEM.Util`类的两个类方法在本地日期和时间与UTC日期和时间之间进行转换:`UTCtoLocalWithZTIMEZONE()`和`LocalWithZTIMEZONEtoUTC()`。这些方法受`$ZTIMEZONE`更改的影响。 ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE1() ClassMethod ZTIMEZONE1() { WRITE $SYSTEM.Util.UTCtoLocalWithZTIMEZONE($ZTIMESTAMP),! WRITE $HOROLOG,! WRITE $SYSTEM.Util.LocalWithZTIMEZONEtoUTC($H),! WRITE $ZTIMESTAMP } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE1() 65785,66819.613 65785,66819 65785,38019 65785,38019.614 ``` # 示例 以下示例返回当前时区: ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE2() ClassMethod ZTIMEZONE2() { SET zone=$ZTIMEZONE IF zone=0 { WRITE !,"时区是格林威治标准时间" } ELSEIF zone>0 { WRITE !,"时区是 ",zone/60," 格林威治以西" } ELSE { WRITE !,"时区是 ",(-zone)/60," 格林威治以东" } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE2() 时区是 8 格林威治以东 ``` 以下示例显示了设置时区可以更改日期和时间: ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE3() ClassMethod ZTIMEZONE3() { SET zonesave=$ZTIMEZONE WRITE !,"当前时区的日期: ",$ZDATE($HOROLOG) IF $ZTIMEZONE=0 { SET $ZTIMEZONE=720 } ELSEIF $ZTIMEZONE>0 { SET $ZTIMEZONE=($ZTIMEZONE-720) } ELSE { SET $ZTIMEZONE=($ZTIMEZONE+720) } WRITE !,"Date halfway around the world: ",$ZDATE($HOROLOG) WRITE !,"格林威治天文台的日期: ",$ZDATE($ZTIMESTAMP) SET $ZTIMEZONE=zonesave } ``` ```java DHC-APP> d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE3() 当前时区的日期: 02/10/2021 Date halfway around the world: 02/10/2021 格林威治天文台的日期: 02/10/2021 ``` 以下示例确定本地时间是否与时区时间相同: ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE4() ClassMethod ZTIMEZONE4() { SET localnow=$HOROLOG, stamp=$ZTIMESTAMP WRITE !,"当地日期和时间: ",$ZDATETIME(localnow,1,1) SET clocksecs=$PIECE(localnow,",",2) SET stampsecs=$EXTRACT(stamp,7,11)-($ZTIMEZONE*60) IF clocksecs=stampsecs { WRITE !,"没有本地时间变量:" WRITE !,"本地时间是时区时间" } ELSE { IF clocksecs=stampsecs+3600 { WRITE !,"夏令时变体:" WRITE !,"从时区时间偏移1小时的本地时间" } ELSE { WRITE !,"当地时间和时区时间为" WRITE !,(clocksecs-stampsecs)/60," 分钟不同" } } QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMEZONE4() 当地日期和时间: 02/10/2021 18:40:21 没有本地时间变量: 本地时间是时区时间 ```
文章
姚 鑫 · 六月 16, 2021

第九章 将XML导入到对象中

# 第九章 将XML导入到对象中 本章介绍如何使用%XML.Reader将XML文档导入到 IRIS对象中。 **注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码, IRIS将使用前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。** 还可以使用`%XML.Reader`读取任意XML文档并返回DOM(文档对象模型)。 # 创建XML读取器概述 IRIS提供了一些工具,用于读取XML文档并创建与该文档的元素相对应的启用XML的 IRIS对象的一个或多个实例。基本要求如下: - 该对象的类定义必须扩展`%XML.Adaptor`。除了少数例外,该对象引用的类还必须扩展`%XML.Adaptor`。 提示:如果相应的XML模式可用,可以使用它来生成类(以及任何支持的类)。 - 要导入XML文档,创建`%XML.Reader`的实例,然后调用该实例的方法。这些方法指定XML源文档,将XML元素与启用XML的类相关联,并将源中的元素读取到对象中。 `%XML.Reader`使用类中的`%XML.Adaptor`提供的方法执行以下操作: - 它使用InterSystems IRIS SAX接口解析和验证传入的XML文档。验证可以包括DTD或XML架构验证。 - 它确定是否有任何启用了XML的对象与XML文档中包含的元素相关,并在读取文档时创建这些对象的内存中实例。 **请注意,`%XML.Reader`创建的对象实例不存储在数据库中;它们是内存中的对象。如果要将对象存储在数据库中,则必须调用%Save()方法(对于持久对象),或者将相关属性值复制到持久对象并保存它。应用程序还必须决定何时插入新数据和何时更新现有数据;`%XML.Reader`无法进行此区分。** 下面的终端会话显示了一个简单的示例。在这里,我们将XML文件读入一个新对象,检查该对象,然后保存该对象: ```java /// w ##class(PHA.TEST.Xml).ReadXml() ClassMethod ReadXml() { Set reader = ##class(%XML.Reader).%New() Set file="E:\temp\samplePerson.xml" Set status = reader.OpenFile(file) if $$$ISERR(status) { do $System.Status.DisplayError(status) quit } Write status,! Do reader.Correlate("Person","Sample.Person") Do reader.Next(.object,.status) if $$$ISERR(status) { do $System.Status.DisplayError(status) quit } Write object.Name,! Do object.%Save() q "" } ``` 此示例使用以下示例XML文件: ```java Worthington,Jeff R. 1976-11-03 Elm City 27820 Best,Nora A. Weaver,Dennis T. ``` # 创建导入方法 ## 总体方法结构 方法应按以下顺序执行以下部分或全部操作: 1. 创建`%XML.Reader`的实例。 2. 也可以指定此实例的`Format`属性,以指定要导入的文件的格式。 默认情况下,InterSystems IRIS假定XML文件为文字格式。如果文件是SOAP编码格式,则必须指明这一点,以便可以正确读取该文件。 3. 可以选择设置此实例的其他属性。 4. 请使用`%XML.Reader`的以下方法之一 - `OpenFile()` -打开文件。 - `OpenStream()`-打开一个流。 - `OpenString()` -打开一个字符串。 - `OpenURL()` -打开一个URL。 在每种情况下,可以选择性地为该方法指定第二个参数,以覆盖`Format`属性的值。 5. 将这个文件中的一个或多个XML元素名与具有相应结构的支持InterSystems IRIS XML的类关联起来。 有两种方法可以做到这一点: - 使用`Correlate()`方法,它有以下签名: ``` method Correlate(element As %String, class As %String, namespace As %String) ``` 其中`element`是XML元素名,class是InterSystems IRIS类名(带包),`namespace`是可选的名称空间`URI`。 如果使用`namespace`参数,则匹配仅限于指定命名空间中的指定元素名。 如果将命名空间参数指定为"",则与`Next()`方法中给出的默认命名空间相匹配。 如果不使用`namespace`参数,则只使用元素名进行匹配。 提示:可以反复调用`Correlate()`方法来关联多个元素。 - 使用`CorrelateRoot()`方法,它有以下签名: ``` method CorrelateRoot(class As %String) ``` 其中class是InterSystems IRIS类名(带包)。此方法指定XML文档的根元素与指定的类相关。 6. 按如下方式实例化类实例: 如果使用`Correlate()`,则遍历文件中的相关元素,一次循环一个元素。在循环中,使用Next()方法,该方法具有以下签名: ``` method Next(ByRef oref As %ObjectHandle, ByRef sc As %Status, namespace As %String = "") as %Integer ``` 其中`OREF`是该方法创建的对象,`sc`是状态,`Namespace`是文件的默认名称空间。 - 如果使用`CorrelateRoot()`,请调用`next()`方法一次,这会导致实例化相关类。 `Next()`方法在到达文件末尾时返回0。如果在此之后再次调用`next()`,则将从文件顶部开始再次循环遍历文件中的对象。(指定的关联仍然有效。) ## 错误检查 **上一节提到的大多数方法都返回状态。应该在每个步骤之后检查状态,并在适当的情况下退出。** ## 基本导入示例 名为`test.xml`的以下XML文件: ```xml 姚 鑫 ``` 我们首先定义一个启用XML的类`MyApp.Person`,它是`Person`的对象表示: ```java Class MyApp.Person Extends (%Persistent, %XML.Adaptor) { Parameter XMLNAME = "Person"; Property Name As %String; Storage Default { %%CLASSNAME Name ^MyApp.PersonD PersonDefaultData ^MyApp.PersonD ^MyApp.PersonI ^MyApp.PersonS %Library.CacheStorage } } ``` 要将此文件导入到`MyAppPerson`类的实例中,我们可以编写以下方法: ```java /// w ##class(PHA.TEST.Xml).ImportXml() ClassMethod ImportXml() { // 创建%XML.Reader的实例 Set reader = ##class(%XML.Reader).%New() // 开始处理文件 Set status = reader.OpenFile("E:\temp\testPerson.xml") If $$$ISERR(status) {do $System.Status.DisplayError(status)} // 将类名与XML元素名相关联 Do reader.Correlate("Person","MyApp.Person") // 从XML文件读取对象 While (reader.Next(.object,.status)) { Write object.Name,! } // 如果在处理过程中发现错误,则将其显示 If $$$ISERR(status) {do $System.Status.DisplayError(status)} q "" } ``` ```java DHC-APP>w ##class(PHA.TEST.Xml).ImportXml() 姚 鑫 ``` 此方法执行几个任务: - 它使用InterSystems IRIS `SAX`接口解析输入文件。这包括根据文档的DTD或架构(如果指定)验证文档。 - `Correlate()`方法将类`MyApp`关联起来。 `MyPerson`与XML元素``; ``中的每个子元素都成为`MyPerson`的一个属性。 - 它从输入文件中读取每个``元素,直到没有剩余元素。 - 最后,如果循环因错误而终止,则该错误将显示在当前输出设备上。 如上所述,此示例不将对象存储到数据库。因为`MyPerson`是持久对象,所以可以通过在`While`循环中添加以下行来完成此操作: ```java /// w ##class(PHA.TEST.Xml).ImportXml() ClassMethod ImportXml() { // 创建%XML.Reader的实例 Set reader = ##class(%XML.Reader).%New() // 开始处理文件 Set status = reader.OpenFile("E:\temp\testPerson.xml") If $$$ISERR(status) {do $System.Status.DisplayError(status)} // 将类名与XML元素名相关联 Do reader.Correlate("Person","MyApp.Person") // 从XML文件读取对象 While (reader.Next(.object,.status)) { Write object.Name,! Set savestatus = object.%Save() If $$$ISERR(savestatus) {do $System.Status.DisplayError(savestatus)} } // 如果在处理过程中发现错误,则将其显示 If $$$ISERR(status) {do $System.Status.DisplayError(status)} q "" } ``` ![image](6AAE53278D13494E959BC90CC739F33C) ## 通过HTTPS URL访问文档 对于`OpenURL()`方法,如果文档位于需要`SSL/TLS`的`URL`,请执行以下操作: 1. 使用管理门户创建包含所需连接详细信息的`SSL/TLS`配置。这是一次性的步骤。 2. 使用`%XML.Reader`时,请设置读取器实例的`SSLConfiguration`属性。对于该值,请指定在上一步中创建的SSL/TLS配置的名称。 或者,当使用`%XML.Reader`,还可以执行以下操作: 1. 创建`%Net.HttpRequest`实例。 2. 将该实例的`SSLConfiguration`属性设置为等于管理门户中创建的`SSL/TLS`配置的配置名称。 3. 使用`%Net.HttpRequest`的实例作为`OpenURL()`的第三个参数。 例如: ![image](31D8EC126E9F4C3D92C558742C3FC0B7) ```java Class YX.Config Extends (%Persistent, %XML.Adaptor) { Parameter XMLNAME = "update"; Property version As %String; Property name As %String; Property url As %String; } ``` ```java /// 请求http的xml,映射到本地类 /// w ##class(PHA.TEST.Xml).ReadXmlHttp("http://192.168.10.3/dthealth/web/csp/version.xml") ClassMethod ReadXmlHttp(url) { set reader = ##class(%XML.Reader).%New() set request = ##class(%Net.HttpRequest).%New() set request.SSLConfiguration="yx" set status = reader.OpenURL(url,,request) If $$$ISERR(status) {do $System.Status.DisplayError(status)} // 将类名与XML元素名相关联 Do reader.Correlate("update","YX.Config") While (reader.Next(.object,.status)) { Write object.version,! Write object.name,! Write object.url,! } q "" } ``` ```java DHC-APP>w ##class(PHA.TEST.Xml).ReadXmlHttp("http://192.168.10.3/dthealth/web/csp/version.xml") 27 Herb http://192.168.31.124/dthealth/web/csp/Herb.apk ``` ### 在服务器需要身份验证时访问文档 **如果服务器需要身份验证,请创建`%Net.HttpRequest`的实例,并设置该实例的用户名和密码属性。还可以如上所述使用SSL(因此还要设置`SSLConfiguration`属性)。然后使用`%Net.HttpRequest`的实例作为`OpenURL()`的第三个参数,如上例所示。**