用友UAP开发NC65向导主子表单据:从零到跑通第一条业务链

发布时间:2026/10/20 23:34:27
用友UAP开发NC65向导主子表单据:从零到跑通第一条业务链 简介这份资源面向用友UAP NC65平台的初学者与二次开发人员聚焦向导式开发主子表单据这一典型场景帮助读者理解主表与子表的一对多关联、数据绑定与联动交互逻辑。包内共126个文件以37个java源码与37个class编译文件为核心辅以16个sql建表脚本、6个properties配置、5个xml及bmf、bpf、btf等UAP专有元数据文件另有pdf说明与xlsx表格压缩包约4.56MB结构完整便于对照学习。目前已有1675人学习下载说明该主题在企业级应用开发群体中关注度较高。读者可从中获取可运行的示例工程、数据库脚本与配置模板结合源码理解事件处理、数据绑定与增删改查的实现方式并借助测试调试与优化思路快速掌握主子表单据从建表、布局到联动的完整开发流程为后续复杂项目打下基础。1. 用友UAP开发NC65向导主子表单据从零到能跑通的第一条业务链很多做NC65二次开发的人第一次接触UAP向导式主子表单据都会卡在同一个地方单据模板建好了元数据也生成了但主子表之间的关联字段死活对不上保存时报“主键为空”或者“子表数据丢失”。这不是玄学是向导生成的默认配置和实际业务模型之间有断层。用友UAP开发NC65向导主子表单据的详细步骤核心要解决的就是三件事主表与子表的物理模型怎么建、向导生成的代码骨架怎么改、单据模板与元数据的绑定关系怎么校验。适合已经了解NC65基本架构、能独立登录UAP开发平台、手头有一个明确主子表业务场景比如订单-明细、合同-条款的开发者。下面按实际动手顺序拆开讲每一步都给出可复现的操作和参数。2. 主子表模型设计先定数据库结构再动向导2.1 主表与子表的字段规划原则在UAP里建主子表单据第一步不是打开向导而是把数据库表结构想清楚。主表存单据头信息子表存行项目信息两者通过主表主键关联。常见做法是主表用PK_主表名作为主键子表用PK_子表名作为自身主键同时用PK_主表名作为外键指向主表。字段类型上主表主键用CHAR(20)子表外键同类型同长度否则向导生成关联时会报类型不匹配。我一般会先在数据库里把两张表建好再进UAP做元数据。这样做的好处是向导读取表结构时直接映射不用在向导里手动补字段。建表时注意主表必须有TS时间戳字段和DR删除标记字段子表同样需要这是NC65持久化框架的硬性要求。如果漏了后续生成VO时框架会报字段缺失。-- 主表示例采购订单头 CREATE TABLE PO_ORDER_H ( PK_ORDER_H CHAR(20) NOT NULL PRIMARY KEY, ORDER_CODE VARCHAR2(50), SUPPLIER VARCHAR2(100), ORDER_DATE CHAR(19), TS CHAR(19), DR NUMBER(10) DEFAULT 0 ); -- 子表示例采购订单行 CREATE TABLE PO_ORDER_B ( PK_ORDER_B CHAR(20) NOT NULL PRIMARY KEY, PK_ORDER_H CHAR(20), MATERIAL_CODE VARCHAR2(50), QUANTITY NUMBER(18,4), PRICE NUMBER(18,4), TS CHAR(19), DR NUMBER(10) DEFAULT 0 );上面建表语句里PK_ORDER_H在主表和子表都出现主表里是主键子表里是外键。TS和DR是NC65所有业务表的标配TS用于乐观锁DR用于逻辑删除。字段长度和类型一旦定了后面向导里不要改改了要重新生成元数据。2.2 在UAP中注册数据源与元数据表建好后登录UAP开发平台进入“数据建模”模块。先注册数据源指向你建表的那个数据库实例。数据源配置里数据库类型选Oracle或SQLServer连接URL按实际填用户名密码用建表那个schema的。注册完数据源后新建元数据选择“从数据库表导入”把刚才建的两张表勾上。导入时注意主表和子表要分别导入不要试图一次导入成主子结构。导入完成后在元数据列表里能看到两张独立的实体。接下来手动建立关联打开主表元数据在“子实体”属性里添加子表关联字段选PK_ORDER_H对PK_ORDER_H。这一步是向导识别主子表的关键如果这里没配后面向导生成的就是两张单表。配完关联后检查主表元数据的“主键生成策略”一般选“自定义主键”或“序列”不要选“数据库自增”因为NC65的持久化框架需要自己控制主键。子表元数据里PK_ORDER_H字段要标记为“外键”这样向导生成VO时才会带上父子引用。2.3 向导生成前的检查清单打开向导之前确认这几项主表元数据里子实体已关联、主键策略已设、TS和DR字段已映射子表元数据里外键已标记、主键已设。另外两张表的字段注释最好在数据库里就写好向导会把注释带到界面标签上省得后面一个个改。还有一个容易忽略的点元数据里的“显示名”和“字段名”要区分开。显示名是界面上看到的字段名是数据库列名。向导生成界面时用显示名生成VO时用字段名。如果显示名里有空格或特殊字符界面可能显示异常建议显示名用中文字段名用英文大写加下划线。3. 向导配置与代码生成把默认骨架改成能用的3.1 启动向导并选择主子表模板在UAP开发平台里右键点击项目节点选择“新建”-“单据向导”。向导第一步选模板类型这里要选“主子表单据”不要选“单表”或“树形”。选完后向导会让你指定主表和子表的元数据。主表选PO_ORDER_H子表选PO_ORDER_B关联字段向导会自动带出来确认是PK_ORDER_H即可。下一步是配置界面布局。向导默认给主表字段放上半部分子表字段放下半部分表格。我一般会把主表里不需要用户输入的字段比如TS、DR、主键隐藏掉只留业务字段。子表表格里把外键字段也隐藏因为保存时框架会自动填充。这一步在向导的“字段可见性”里勾选不要生成后再去代码里改改起来麻烦。3.2 生成后的代码结构解析向导完成后会在项目里生成一组文件主表VO、子表VO、主表DAO、子表DAO、主表Service、子表Service、主表Controller、子表Controller以及界面配置文件。目录结构一般是src/模块名/单据名/下面分vo、dao、service、controller四个包。主表VO里会有一个List子表VO类型的属性名字通常是子表VO类名首字母小写加s。这个属性是主子表联动的核心保存时框架会遍历这个List把子表数据一起持久化。如果向导生成后这个List没出现说明元数据关联没配好要回去检查。// 主表VO示例向导生成后手动补充注释 public class OrderHVO { private String pkOrderH; // 主键 private String orderCode; // 单据编号 private String supplier; // 供应商 private String orderDate; // 单据日期 private String ts; // 时间戳 private Integer dr; // 删除标记 private ListOrderBVO orderBs; // 子表集合向导自动生成 // getter/setter省略 }上面代码里orderBs就是子表集合。向导生成时如果元数据关联正确这个属性会自动加上。如果没有手动加一个ListOrderBVO属性名字要和子表DAO里查询时用的名字一致。3.3 主键生成与子表外键填充的代码修改向导生成的代码里主键生成逻辑默认是空的需要手动补。我一般会在主表Service的insert方法里用UUID或序列生成主键然后遍历子表集合把主键赋给子表的外键字段。这一步不做保存时子表外键为空数据库里就是脏数据。// 主表Service的保存方法片段 public void saveOrder(OrderHVO orderH) { // 生成主键 String pk UUID.randomUUID().toString().replace(-, ).substring(0, 20); orderH.setPkOrderH(pk); // 遍历子表填充外键 if (orderH.getOrderBs() ! null) { for (OrderBVO orderB : orderH.getOrderBs()) { orderB.setPkOrderH(pk); // 外键指向主表主键 orderB.setPkOrderB(UUID.randomUUID().toString().replace(-, ).substring(0, 20)); } } // 调用DAO保存主表 orderHDao.insert(orderH); // 调用DAO批量保存子表 if (orderH.getOrderBs() ! null) { for (OrderBVO orderB : orderH.getOrderBs()) { orderBDao.insert(orderB); } } }这段代码里主键用UUID截取20位和数据库CHAR(20)对齐。子表外键pkOrderH直接赋主表主键值子表自身主键也生成一个。注意insert顺序先主表后子表因为子表外键依赖主表主键。如果数据库有外键约束顺序反了会报错。3.4 界面配置与事件绑定向导生成的界面配置文件里主表字段和子表表格已经绑定了VO属性。需要手动加的是按钮事件保存、新增行、删除行。保存按钮绑定主表Controller的save方法新增行按钮绑定子表Controller的addRow方法删除行绑定deleteRow。这些在界面设计器里通过“事件绑定”面板操作选对应按钮选事件类型填方法名。新增行时子表Controller里要创建一个新的子表VO实例加到主表VO的子表集合里然后刷新表格。删除行时从集合里移除选中行再刷新。刷新表格用table.refresh()或重新设数据源。这一步不写界面上点新增没反应。4. 避坑与排查主子表单据开发中最容易翻车的五个点4.1 保存时报“主键为空”现象点保存后后台日志报主键字段为空数据没进库。原因主表Service里没生成主键或者生成后没设到VO里。解决在保存方法第一行就生成主键并set进去确保后续子表外键填充用的是同一个值。如果用了序列检查序列是否已创建、是否有权限。4.2 子表数据保存后丢失现象主表保存成功子表数据库里没数据。原因子表集合为空或者子表DAO的insert没被调用。解决先在保存方法里打印子表集合大小确认界面传过来的数据有没有进集合。如果集合有数据但没入库检查子表DAO的insert方法是否被正确调用以及子表外键是否已填充。外键为空时有些框架会静默跳过。4.3 界面子表表格不显示数据现象查询主表后子表表格空白。原因查询方法只查了主表没查子表或者查了但没设到主表VO的子表集合里。解决在主表查询方法里根据主表主键再查一次子表把结果set到主表VO的orderBs属性。查询子表时用外键字段过滤不要用主键。4.4 修改时子表数据重复插入现象修改主表后保存子表数据多了一份。原因保存方法里对子表只做了insert没做delete再insert或者没判断是新增还是修改。解决修改场景下先根据主表主键删除子表旧数据再插入新数据。或者用update逐条更新但要注意子表主键是否变化。4.5 向导生成后元数据关联丢失现象重新打开向导或重新生成代码后主子表关联没了。原因元数据里子实体关联被覆盖或者向导缓存了旧配置。解决每次重新生成前先备份元数据配置。如果关联丢了手动在元数据里重新配子实体再生成。不要依赖向导的“记住上次配置”那个功能在跨会话时不可靠。5. 进阶技巧用模板化配置减少重复劳动5.1 把常用字段和事件抽成模板做多了会发现主子表单据的很多字段和事件是重复的单据编号、单据日期、制单人、制单时间这些几乎每个单据都有。我一般会在UAP里建一个“基础字段模板”把这些字段的元数据定义好新建单据时直接引用模板不用每次手动加。事件绑定也一样保存、新增行、删除行的代码逻辑基本固定可以抽成一个基类子类继承后只改业务字段。// 基类示例封装主子表通用保存逻辑 public abstract class BaseOrderService { public void saveOrder(BaseOrderHVO orderH) { String pk generatePk(); orderH.setPkOrderH(pk); fillChildFk(orderH, pk); saveMain(orderH); saveChildren(orderH); } protected abstract void saveMain(BaseOrderHVO orderH); protected abstract void saveChildren(BaseOrderHVO orderH); protected abstract void fillChildFk(BaseOrderHVO orderH, String pk); private String generatePk() { return UUID.randomUUID().toString().replace(-, ).substring(0, 20); } }这个基类把主键生成、外键填充、保存顺序固定下来子类只实现具体的主表和子表保存逻辑。这样新单据开发时Service层代码量能减少一半以上。注意基类里的BaseOrderHVO要定义子表集合的通用接口比如getChildren()和setChildren()让子类VO实现。5.2 用校验规则拦截脏数据保存前加校验能避免很多后期排查。我一般会在主表Service的保存方法开头加几行校验主表必填字段是否为空、子表集合是否为空、子表每行的数量是否大于零。校验不通过直接抛异常界面上弹提示。这样比数据进库后再去数据库里捞要高效得多。校验规则可以配在元数据里也可以写在代码里。元数据里配的校验向导生成界面时会自动加前端校验但后端还是要再校验一次防止绕过界面直接调接口。代码里的校验用if判断加throw new BusinessException(提示信息)异常信息会传到前端显示。5.3 查询性能优化子表分页与懒加载主子表单据查询时如果子表数据量大一次性全查出来会拖慢界面。我一般会在子表查询里加分页默认每页50条界面上加翻页按钮。或者用懒加载主表查出来后子表先不查等用户点开某行主表时再查对应子表。UAP的表格组件支持懒加载模式配置一下lazyLoad属性即可。分页查询的SQL里用ROWNUM或LIMIT控制返回条数同时查一个总数用于显示页码。注意分页参数要传到后端前端表格的onPageChange事件里调查询方法把当前页码和每页条数传过去。这一步不做子表数据超过几百条时界面会明显卡顿。5.4 用日志定位保存失败的具体字段保存失败时后台日志往往只报一个异常堆栈不告诉你哪个字段有问题。我习惯在保存方法里加一段日志把主表和子表的关键字段值打印出来尤其是主键、外键、必填字段。这样一旦报错看日志就能定位到具体是哪条数据、哪个字段。// 保存前打印关键字段日志 logger.info(保存主表pk{}, code{}, supplier{}, orderH.getPkOrderH(), orderH.getOrderCode(), orderH.getSupplier()); if (orderH.getOrderBs() ! null) { for (OrderBVO orderB : orderH.getOrderBs()) { logger.info(子表行pk{}, fk{}, material{}, qty{}, orderB.getPkOrderB(), orderB.getPkOrderH(), orderB.getMaterialCode(), orderB.getQuantity()); } }日志级别用info就行不要用debug因为生产环境一般不开debug。打印时注意不要打印敏感信息比如价格、客户名称可以用掩码或只打印长度。这段日志在排查“子表外键为空”或“主键重复”时特别有用比断点调试快。5.5 版本兼容不同NC65小版本的向导差异NC65有几个小版本UAP向导的界面和生成代码略有差异。比如某些版本里子表集合的属性名是children某些版本是子表VO类名首字母小写加s。生成代码后先看一眼属性名和DAO里查询时用的名字对不上就手动改。另外元数据导入时某些版本对TS字段的类型要求是CHAR(19)某些版本接受TIMESTAMP建表时统一用CHAR(19)最保险。如果团队里有人用不同小版本建议在项目文档里记下当前使用的NC65具体版本号和UAP补丁号。遇到向导行为不一致时先核对版本不要盲目改代码。我吃过这个亏在一个版本上调通的配置换到另一个版本上生成出来少了一个关联属性查了半天以为是元数据问题最后发现是版本差异。希望帮到你。本文还有配套的精品资源点击获取