简介:面向Teamcenter平台二次开发与SOA服务端开发人员,这份资源以Java工具类示例为载体,演示在Teamcenter环境中通过SOA服务封装核心业务操作的方法,适合需要实现系统集成、流程自动化、服务重用的企业IT项目直接参考。压缩包为RAR格式,共1个文件,即封装SOA操作的Java源码,包体仅7KB,属于高度聚焦的轻量级工具类,便于快速阅读、移植或嵌入自身代码。已有851人学习,说明其在Teamcenter SOA开发社区具备一定实用价值,尤其适合需要在短时间内掌握服务端封装技巧的工程师。工具类围绕Item创建、Folder管理、对象属性查询等高频操作进行封装,基于WSDL接口描述生成客户端代理,完整展示了从服务定义、代理生成到Java调用的落地写法,可作为二次开发脚手架直接参考,帮助开发者少走弯路,降低集成测试和排错成本。
1. 认识Teamcenter SOA:为什么说它是PLM二次开发的真正入口
做过Teamcenter二次开发的人都有过这种经历:拿到一个需求,比如"把ERP里的物料编码同步到Teamcenter的Item上",第一反应是去写ITK或者用Active Workspace的JavaScript,结果不是编译环境折腾半天,就是权限调不通。其实Teamcenter从较新版本开始,官方推荐的开发方式已经是SOA(Service Oriented Architecture)了。你可以把它理解成Teamcenter对外暴露的一套Web Service接口层,跟底层数据库打交道的活全被封装好了,你只需要调服务、传参数、收结果。这份资源整理的正是SOA开发从零到落地的东西,适合正在从ITK转向SOA、或者刚接手Teamcenter二开的工程师,尤其是做系统集成、批量数据导入导出、客户端工具开发的人。
2. SOA开发前的认知铺垫:架构、环境与准备清单
2.1 Teamcenter SOA的定位:它和ITK、RAC到底什么关系
先说结论:SOA是Teamcenter在传统ITK接口之上重新封装的一层服务接口,走的是HTTP/HTTPS协议,数据格式是XML或JSON。ITK是C语言写的底层API,编译成DLL之后挂在Teamcenter客户端里跑;RAC(Rich Application Client)是Java写的胖客户端框架。SOA直接面向程序员提供的是Java或C++ Web Service接口,语言门槛低得多,调试也方便。
很多从ITK转过来的工程师容易踩一个认知坑:以为SOA只是把ITK换个包装。实际上SOA的编程模型完全不同。ITK是"本地函数调用",你需要考虑客户端和Teamcenter服务器在同一个C++进程空间里的内存管理;SOA是"远程服务调用",你面对的是服务端暴露出来的接口契约,不用管服务端怎么实现,只需要关心发送什么请求、解析什么响应。
SOA的发展背景是Teamcenter逐步Web化的过程。早期Teamcenter是C/S架构,客户端很重;后来Teamcenter逐渐支持B/S架构,SOA起到了中间桥梁的作用。现在做Teamcenter二次开发,SOA已经成了主流选择,特别是做系统集成、数据迁移和自动化处理的时候。
2.2 环境准备:需要装哪些东西、版本怎么对应
做SOA开发,你本地环境需要的东西分成两块:开发环境和运行环境。开发环境指的是写代码的IDE和SDK,运行环境指的是调SOA服务时依赖的客户端库和配置文件。
先说SDK。Teamcenter的客户端安装目录下会有SOA相关的jar包或者dll文件。如果你用Java开发,核心的jar包是tcsoa系列,一般位于%TC_ROOT%\soa目录下。这里有一个容易纠结的问题:不同版本的Teamcenter对应不同版本的SOA SDK,比如Teamcenter 11和Teamcenter 13的SDK包并不完全兼容。你写代码之前一定要确认好版本。
再说配置。最关键的两个文件是tc.properties和tc_services.properties。前者定义了服务地址、端口、协议这些基础信息,后者定义了SOA框架内部的服务超时时间、连接池大小等参数。我一般会在开发环境里单独复制一份修改,不让开发环境的配置去污染生产环境的客户端。
开发工具方面,Java程序员用Eclipse或者IntelliJ IDEA都行。IDE本身不重要,重要的是把SDK的jar包加到classpath里。这里有个建议:不要把整个soa目录下的jar全加进去,只加tcsoa.jar、tcsoaclient.jar、tccore.jar这几个核心包,否则会出现jar包冲突。
# 把SOA相关jar包加入classpath的示例(Windows环境) set SOA_LIB=%TC_ROOT%\soa\lib set CLASSPATH=%SOA_LIB%\tcsoa.jar;%SOA_LIB%\tcsoaclient.jar;%SOA_LIB%\tccore.jar;%CLASSPATH%这段批处理命令做的事情是把SOA框架的核心jar包追加到CLASSPATH里。%TC_ROOT%是Teamcenter安装根目录的环境变量,不同机器上路径不一样,你根据实际安装路径改一下就行。tcsoa.jar是SOA框架的基础库,里面定义了服务调用的核心类;tcsoaclient.jar是客户端API,封装了建立连接、发送请求、处理响应这些逻辑;tccore.jar包含一些公共工具类和异常类。
2.3 先跑通一个最小调用:验证环境是通的
第一次做SOA开发,建议不要直接去写业务逻辑,先把一个最小的服务调用跑通。这个最小调用做的就是"连接服务器 + 查询当前用户信息"这两件事,作用是验证你前面配的环境是通的。
import com.teamcenter.soa.client.Connection; import com.teamcenter.soa.client.FileManagementUtility; import com.teamcenter.services.strong.core.SessionService; import com.teamcenter.services.strong.core._2006_03.Session; import com.teamcenter.soa.exceptions.CredentialsException; public class FirstSOA { public static void main(String[] args) { // 初始化连接参数 Connection connection = new Connection("http://localhost:7001/tc", "infodba", "infodba", "mygroup", "myproject"); try { connection.connect(); // 获取会话服务,这是最基础的服务之一 SessionService sessionService = SessionService.getService(connection); Session.SessionInfo sessionInfo = sessionService.getSessionInfo(); System.out.println("连接成功,当前用户: " + sessionInfo.user.uid); } catch (CredentialsException e) { System.err.println("登录失败, 请检查用户名密码: " + e.getMessage()); } finally { connection.logout(); } } }这段代码的逻辑很简单:创建连接对象、传服务地址、用户名、密码、用户组和项目,然后调用SessionService获取会话信息。getSessionInfo返回的是一个SessionInfo结构,里面包含了当前用户的ID、组、角色这些基本信息。finally块里的logout()一定要写,否则每次运行都会在服务器上留下一个未释放的会话,开发的时候感觉不到,跑到生产环境就会把连接池占满。
这里要提一个容易忽略的点:Connection构造函数的参数顺序不能记错,分别是URL、用户名、密码、组、项目。项目如果为空字符串,Teamcenter会使用用户的默认项目。我遇到过不少同事把组和项目写反,结果登录成功了但后续操作报权限错误,排查半天找到的是这种低级问题。
3. 核心开发实战:查询、创建与修改对象的完整代码
3.1 查询对象:用QueryService把对象从数据库里捞出来
Teamcenter SOA里最常用的服务之一是QueryService。这个服务对应的业务能力是:根据查询条件返回对象列表。常见场景是把Item、ItemRevision等业务对象从数据库里查出来供后续操作。
做查询的时候要理解两个概念:一个是"查询条件"(where条件),一个是"查询类型"(要查什么对象)。SOA的查询接口支持多种查询方式,最基本的是search方法,传进去一个SearchQuery结构。
import com.teamcenter.services.strong.query.QueryService; import com.teamcenter.services.strong.query._2008_06.Search; import com.teamcenter.services.strong.query._2008_06.SOATypeFilters; import com.teamcenter.schemas.soa._2008_06.query.Session; import com.teamcenter.schemas.soa._2006_03.typedsession.SessionInfo; import com.teamcenter.soa.client.Connection; public class QueryDemo { public static void main(String[] args) throws Exception { Connection conn = new Connection("http://localhost:7001/tc", "infodba", "infodba", "mygroup", "myproject"); conn.connect(); // 获取查询服务实例 QueryService queryService = QueryService.getService(conn); // 构建查询对象 Search.SearchQuery searchQuery = new Search.SearchQuery(); searchQuery.maxNumToReturn = 10; // 最多返回10条 searchQuery.criteria = new Search.Criteria[1]; Search.Criteria criterion = new Search.Criteria(); criterion.attribute = "item_id"; // 按Item ID查询 criterion.operator = "="; criterion.value = "A001"; // 你要查的ID值 criterion.type = Search.CriteriaType.STRING; searchQuery.criteria[0] = criterion; // 指定查询的对象类型 SOATypeFilters typeFilters = new SOATypeFilters(); typeFilters.types = new String[] { "Item" }; searchQuery.typeFilters = typeFilters; // 执行查询 Search.SearchResponse response = queryService.search(searchQuery); // 遍历查询结果 if (response.searchResult == null) { System.out.println("没有找到匹配的对象"); } else { for (Search.SearchResultObject result : response.searchResult) { System.out.println("找到对象: " + result.objectUid); } } conn.logout(); } }这段代码的核心在于SearchQuery结构体的配置。maxNumToReturn控制返回条数上限,不设置的话默认返回50条,建议在批量处理的场景下显式设置,避免查询结果过大拖垮内存。criteria是查询条件的数组,支持多个条件组合,条件之间默认是AND的关系。attribute字段填的是你查询的属性名,比如item_id、object_name。operator支持=、>、<、LIKE这些常见操作符,模糊查询用LIKE,注意通配符是*和?,不是SQL里的%和_。
查询返回结果里每个SearchResultObject有一个objectUid,这个UID是对象的唯一标识,后续几乎所有操作——更新、删除、关联——都需要这个UID作为入参。所以查询这一步看似简单,实际上是整个SOA开发的基石。建议你的工具类里单独封装一个"按item_id返回UID"的方法,后期会反复用到。
3.2 创建对象:用ItemService新建一条物料记录
创建对象是SOA开发的高频操作。创建一条Item记录,核心逻辑是调用ItemService的createItems方法,同时准备属性数据和BOM视图信息。
import com.teamcenter.services.strong.core.ItemService; import com.teamcenter.services.strong.core._2013_05.Item; import com.teamcenter.soa.client.Connection; public class CreateItemDemo { public static void main(String[] args) throws Exception { Connection conn = new Connection("http://localhost:7001/tc", "infodba", "infodba", "mygroup", "myproject"); conn.connect(); ItemService itemService = ItemService.getService(conn); // 准备创建Item所需的数据 Item.CreateItemInput createInput = new Item.CreateItemInput(); createInput.containerType = "MyContainer"; // 容器类型 createInput.containerName = "MyProject"; // 容器名称 createInput.itemType = "Item"; // 对象类型 // 设置属性值 createInput.attributes = new com.teamcenter.schemas.soa._2006_03.foundation.Attribute[3]; com.teamcenter.schemas.soa._2006_03.foundation.Attribute attr1 = new com.teamcenter.schemas.soa._2006_03.foundation.Attribute(); attr1.name = "item_id"; attr1.value = "B001"; // 物料编码 createInput.attributes[0] = attr1; com.teamcenter.schemas.soa._2006_03.foundation.Attribute attr2 = new com.teamcenter.schemas.soa._2006_03.foundation.Attribute(); attr2.name = "object_name"; attr2.value = "测试物料"; // 物料名称 createInput.attributes[1] = attr2; // 调用创建服务 Item.CreateItemsResponse response = itemService.createItems(new Item.CreateItemInput[] { createInput }); // 检查返回值 if (response.item != null && response.item.length > 0) { String newItemUid = response.item[0].uid; System.out.println("创建成功,新的Item UID: " + newItemUid); } else { System.out.println("创建失败"); if (response.error != null) { System.err.println("错误信息: " + response.error[0].message); } } conn.logout(); } }创建Item的逻辑比查询稍微复杂一点。createInput里containerType和containerName指定了对象放在哪个容器(通常是项目或文件夹)下面。如果你不指定容器,Teamcenter会放到默认容器里,但这样后面做权限管理时会比较麻烦,建议每次都显式指定。
attributes数组是你要给新对象赋的属性值,属于必填项。属性值里item_id和object_name通常都是必填的,某些企业环境还可能要求object_desc、owning_user这些属性,具体看你的数据模型定义。如果需要赋空的属性,不用放进数组里,服务端会自动赋默认值。
创建成功之后,响应里返回的uid一定要保存好。这个UID后面更新、创建ItemRevision、附加数据集全都要用到。我习惯在工具里维护一个"uid和item_id的映射表",避免每次都重新查询。
3.3 修改对象:拿到UID之后更新属性和保存
创建也好、导入也好,物件落库之后的属性修正是最常见的二次操作。SOA里修改属性的核心方法是ItemService的setProperties接口。与创建不同,修改要求你必须先拿到对象的UID,然后通过setProperties来更新指定属性。
import com.teamcenter.services.strong.core.ItemService; import com.teamcenter.services.strong.core._2013_05.Item; import com.teamcenter.schemas.soa._2006_03.foundation.Attribute; import com.teamcenter.soa.client.Connection; public class UpdateItemDemo { public static void main(String[] args) throws Exception { Connection conn = new Connection("http://localhost:7001/tc", "infodba", "infodba", "mygroup", "myproject"); conn.connect(); // 这里假设前面已经通过查询拿到了uid String itemUid = "sUbQuLwYES0SQv"; ItemService itemService = ItemService.getService(conn); // 设置要修改的属性 Attribute[] attributes = new Attribute[2]; Attribute attr1 = new Attribute(); attr1.name = "object_desc"; attr1.value = "这是修改后的描述"; attributes[0] = attr1; Attribute attr2 = new Attribute(); attr2.name = "owning_group"; attr2.value = "design_grp"; // 修改所有者组 attributes[1] = attr2; // 调用setProperties进行修改 Item.SetPropertiesResponse setResp = itemService.setProperties(itemUid, attributes); if (setResp.error == null) { System.out.println("属性更新成功"); } else { System.err.println("更新失败: " + setResp.error[0].message); } conn.logout(); } }setProperties的第一个参数是目标对象的UID,第二个参数是要更新的属性数组。有一点需要注意:setProperties做的是增量更新,只修改你传入的属性,不影响其他已有属性。这一点和ITK里的AOM_set_attribute很相似,不是先删后建,所以很安全。
另外一个实践技巧是:如果你要做大量批量更新,不要循环一次更新一个对象就logout一次,那样性能太差。合理的做法是一次登录、循环处理完所有对象、最后一次logout。连接本身有超时机制,长事务如果超过一定时间没活动会被服务端断开,这个阈值在tc_services.properties里配,默认一般是30分钟。超过这个时间,你的批量任务还没跑完,就会出现"连接被重置"的报错。
4. 文件传输与数据集操作:把文档和模型塞进Teamcenter
4.1 数据集和数据文件的关系:先搞懂这两个东西
Teamcenter里管理文件(比如Office文档、CAD模型、PDF图纸)的方式是"数据集"(Dataset)。数据集本身是一个容器对象,它下面挂着实际的文件(Named Reference)。当你调用SOA上传文件的时候,其实做了两件事:一是创建Dataset对象并关联到Item或ItemRevision上,二是通过文件传输服务把真实的文件写到服务器端的Volume里。
这个机制和普通文件服务器完全不同。Teamcenter里文件不是直接"放在"某个文件夹下的,而是被Dataset"引用"的。删除Dataset并不会立刻删掉物理文件,文件真正被清理要等Teamcenter的purge机制跑完。所以做删数据需求时,不要指望关系删除后文件就没了。
4.2 上传文件:从本地路径到数据集命名引用的完整流程
上传一个文件到Teamcenter并关联到Item Revision上的完整流程,代码可以拆成三步:准备数据引用、创建数据集、传输文件。
import com.teamcenter.services.strong.core.DataManagementService; import com.teamcenter.services.strong.core.DatasetService; import com.teamcenter.services.strong.core._2008_06.Dataset; import com.teamcenter.services.strong.core._2008_06.DataManagement; import com.teamcenter.soa.client.Connection; import com.teamcenter.soa.client.FileTransfer; public class UploadFileDemo { public static void main(String[] args) throws Exception { Connection conn = new Connection("http://localhost:7001/tc", "infodba", "infodba", "mygroup", "myproject"); conn.connect(); // 1. 创建数据集 DatasetService datasetService = DatasetService.getService(conn); Dataset.CreateDatasetInput createInput = new Dataset.CreateDatasetInput(); createInput.containerType = "ItemRevision"; createInput.containerUid = "revisionUid"; // 关联到的ItemRevision的UID createInput.datasetType = "MSWord"; // 数据集类型,对应你配置的dataset type createInput.datasetName = "设计说明文档"; // 数据集名称 Dataset.CreateDatasetsResponse createResp = datasetService.createDatasets(new Dataset.CreateDatasetInput[] { createInput }); String datasetUid = createResp.dataset[0].uid; // 2. 创建命名引用并关联本地文件 Dataset.NamedReferenceInput refInput = new Dataset.NamedReferenceInput(); refInput.datasetUid = datasetUid; refInput.name = "ref"; // 命名引用的名称,按类型模板要求填写 refInput.fileName = "D:\\workspace\\design_doc.docx"; // 本地文件路径 datasetService.createNamedReferences(new Dataset.NamedReferenceInput[] { refInput }); // 3. 启动文件传输(把本地文件上传到服务器) FileTransfer transfer = new FileTransfer(conn); transfer.uploadFiles(); System.out.println("文件上传完成,Dataset UID: " + datasetUid); conn.logout(); } }这段代码里需要注意的三个参数:datasetType、refInput.name、fileName。datasetType必须和Teamcenter服务端配置好的类型一致,比如"MSWord""PDF""JT"等,如果类型不对会报"Dataset类型未配置"的错误。refInput.name填的是命名引用的名称,这个名称在数据集类型模板里定义了,MSWord类型一般叫ref。fileName是本地文件路径,注意必须是绝对路径。
createNamedReferences这一步只是建立了引用关系骨架,还没有真实传输文件。真正的传输动作在transfer.uploadFiles()里发生。这个方法会检查所有待传输的引用,把本地文件推送到服务器端。如果你跳过了上传步骤,数据集就只是个空壳,打不开文件。
4.3 下载文件:照着UID把文件拉回本地
下载的场景通常是自己从Teamcenter里取文件做分析或分发。下载逻辑和上传刚好是反着走:先拿到Dataset的UID和命名引用信息,再通过文件传输服务从服务器把文件下载到本地指定目录。
import com.teamcenter.services.strong.core.DatasetService; import com.teamcenter.services.strong.core._2008_06.Dataset; import com.teamcenter.soa.client.Connection; import com.teamcenter.soa.client.FileTransfer; public class DownloadFileDemo { public static void main(String[] args) throws Exception { Connection conn = new Connection("http://localhost:7001/tc", "infodba", "infodba", "mygroup", "myproject"); conn.connect(); DatasetService datasetService = DatasetService.getService(conn); // 获取数据集信息 Dataset.GetDatasetInfoResponse infoResp = datasetService.getDatasetInfo(new String[] { "datasetUid" }); Dataset.DatasetInfo datasetInfo = infoResp.datasetInfo[0]; // 获取命名引用列表 Dataset.NamedReferenceInfo[] refs = datasetInfo.namedReference; System.out.println("数据集包含 " + refs.length + " 个命名引用"); // 设置下载目录 FileTransfer transfer = new FileTransfer(conn); transfer.setDownLoadDir("D:\\workspace\\download"); // 触发下载 transfer.downloadFiles(); conn.logout(); } }getDatasetInfo能拿到一个数据集的所有信息,包括它的命名引用列表。拿到引用信息后,downloadFiles会自动把所有命名引用对应的物理文件下载到setDownLoadDir指定的本地目录。
这里有一个使用习惯:setDownLoadDir要在downloadFiles调用之前设置。如果你不设置,文件会下载到当前工作目录,容易混入项目目录里。下载完成后,Teamcenter默认不会自动重命名文件。实际下载下来的文件名可能是带前缀的临时文件名,需要你在代码里根据NamedReferenceInfo.fileName字段重新命名。
5. 避坑指南:SOA开发里最常见的翻车现场
5.1 翻车现场一:登录成功但查询无结果
现象:代码跑起来没有任何报错,查query就是返回空数组。
原因:大概率是SearchQuery里的类型名写错了,或者查询的属性名和实际数据模型不一致。比如你写"Item"查不到,但Teamcenter里实际的类型名可能是"MyItem"。另一个常见原因是权限不足,当前登录用户没有对目标数据集的读权限。
解决:先用Teamcenter自带的查询构建器(Query Builder)在客户端里手动执行一遍同样的条件,看看能不能查到数据。能查到说明你的SOA查询条件有语法问题;查不到说明数据模型或权限有问题。查询的type名称可以在QueryService的getQuery方法里拿已有的查询模板看一眼,照着模板里的类型名写最保险。
5.2 翻车现场二:创建对象报"属性tokens不允许修改"
现象:创建Item时在attributes里传了某个属性,服务端直接报错"属性xxx不允许修改"或者"attribute is not modifiable"。
原因:Teamcenter的数据模型里,有些属性是系统控制的,不允许外部直接赋初值。比如creation_date、owning_user,或者说你传的属性里包含了服务端自动生成的内部属性。
解决:把报错属性从attributes数组里去掉,让服务端自己去填。如果你想修改的属性的确是非系统属性但仍然报错,去查一下数据模型里该属性的访问控制设置,看看是否勾了"仅系统可写"。
5.3 翻车现场三:文件上传后数据集打不开
现象:uploadFiles()正常执行完毕,但在Teamcenter客户端里打开Dataset,提示"找不到文件"或者显示文件大小为0。
原因:命名引用里的name写错了。Teamcenter不同Dataset类型对命名引用的名称有硬性要求。比如MSWord类型要求命名引用必须叫ref,如果你写成了document,虽然传输成功,但客户端打开时找不到约定名称的引用,自然打不开。
解决:打开my_XXX_dataset_type对应的Dataset类型定义,查看它要求的命名引用名称。在代码里,命名引用名必须和类型模板里定义的一致,大小写也敏感。
5.4 翻车现场四:连接池句柄泄漏导致服务器卡死
现象:批量处理任务跑了一会儿,服务器响应越来越慢,最后连接全部超时。
原因:代码里创建了Connection但忘了logout。SOA每个连接在服务器端对应一个会话,不主动登出,会话就挂着不释放。开发环境跑几十次可能就有几百个僵尸会话,生产环境更严重。
解决:所有代码路径必须在finally块里执行logout。更推荐的做法是使用连接池,把连接对象缓存复用,不要每次调用都新建。Teamcenter官方提供的ConnectionPool类可以做这件事,但要注意给连接池设置最大连接数和闲置超时时间。
5.5 翻车现场五:本地开发正常,生产环境跑不通
现象:代码在开发环境测试没问题,部署到生产环境直接启动就报错或者登录失败。
原因:多数是配置文件差异。开发环境用的tc.properties配的是localhost地址,生产环境的地址、端口、协议不一样。另一个可能是jar包版本不对,生产环境的Teamcenter版本和开发环境不一致。
解决:写一个env.properties外部配置文件,把服务地址、端口、用户名这些环境相关的东西全抽出来,部署时单独改配置,不要动代码。发布前先用命令行工具tcsoa_ping验证目标环境的SOA服务是否能正常通信,再做代码部署。
6. 进阶实践:批处理性能优化与错误恢复
6.1 复用连接,不要反复登录
批量处理是SOA开发里最常见的场景,比如导入几千条物料、同步几百个BOM。如果每条数据做一次"连接-处理-登出",性能会惨不忍睹。正确做法是一次连接处理完所有数据。
我在实际项目中维护了一个简单的批处理框架:一次登录,循环处理数据,最后登出。记录两个指标——成功条数和失败条数,失败的数据单独落日志,最后统一重试。
int successCount = 0; int failCount = 0; List<String> failMessages = new ArrayList<>(); Connection conn = new Connection(url, user, pass, group, project); conn.connect(); try { for (Map<String, String> data : batchDataList) { try { // 每一条数据走一次创建/更新逻辑 processSingleRecord(conn, data); successCount++; } catch (Exception e) { failCount++; failMessages.add("记录" + data.get("item_id") + "处理失败: " + e.getMessage()); // 不中断循环,继续处理下一条 } } } finally { conn.logout(); } System.out.println("处理完成,成功: " + successCount + ",失败: " + failCount);这段代码体现了批处理的核心思路:单条失败不中断整体任务。注意维护一个failMessages列表,把失败原因记录下来,方便后续统一排查。如果你用的是ArrayList来存批数据,几万条数据量时内存消耗并不大,瓶颈一般出现在数据传输耗时上。
6.2 合理设置超时参数,避免长事务被切断
批处理任务往往需要长时间占用连接。Teamcenter的SOA框架在客户端侧有一个请求超时时间配置,服务端也有对应的IDL超时设置。如果批处理跑起来后客户端一直等不到响应,多半是客户端的超时设置短于服务端实际处理时间。
在tc_services.properties中有一个关键参数soa.request.timeout,默认是30秒。批量插入几千条Item时,单次请求的处理时间完全可能超过30秒。你可以调大它,或者更好的办法是拆批——每500条提交一次,这样单次请求的耗时可控。拆批还有一个好处:如果其中一批失败了,重跑的成本比较低。
6.3 错误恢复:把"重试逻辑"做成标配
失败的请求是否有必要重试?我的判断标准是看错误类型。网络超时、连接重置这类临时性错误值得重试;数据校验错误、权限错误这类确定性错误重试多少遍都没用。所以重试之前先看错误分类。
// 伪代码:带重试的调用逻辑 int maxRetry = 3; for (int attempt = 1; attempt <= maxRetry; attempt++) { try { Item.CreateItemsResponse resp = itemService.createItems(input); if (resp.error == null || resp.error.length == 0) { return resp; // 成功直接返回 } // 服务端返回了业务错误码 if (isPermanentError(resp.error[0].code)) { break; // 确定性的错误,不重试 } } catch (Exception e) { if (attempt == maxRetry) { throw e; } Thread.sleep(1000L * attempt); // 递增等待,避免立刻重试 } }这个重试逻辑有两个要点。第一,isPermanentError用来识别确定性错误。Teamcenter的错误码里,数据校验错误、权限错误属于永久性错误;超时、网络IO异常属于临时性错误。第二,重试之间要有递进间隔(backoff),不要在服务端还没缓过来的时候立刻重试,否则会加剧服务端压力。
6.4 没有后悔药:涉及删除操作之前先做快照
最后说一个关于数据安全的事。SOA的deleteObjects接口可以在几分钟内删掉一片数据。我见过有人在上线脚本时,把删除条件写宽了,把不该删的几百个对象全删了。Teamcenter没有回收站,删掉的对象的物理文件只能通过数据库备份恢复。
从那以后我给自己立了一条规矩:凡是执行删除操作的脚本,必须做两步前置动作。第一步,把要删除对象的UID清单导出来存档。第二步,对生产库做一次轻量备份(Teamcenter支持数据库级别的增量备份)。这样即使删除条件写错,至少可以按UID清单把对象重建出来。希望这条习惯也能帮到你。
本文还有配套的精品资源,点击获取