☰
用友UAP开发NC65向导主子表单据:从零到跑通第一条业务链
2026/10/20 23:34:13 网站建设 项目流程

简介:这份资源面向用友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 List<OrderBVO> orderBs; // 子表集合,向导自动生成 // getter/setter省略 }

上面代码里orderBs就是子表集合。向导生成时如果元数据关联正确,这个属性会自动加上。如果没有,手动加一个List<OrderBVO>属性,名字要和子表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补丁号。遇到向导行为不一致时,先核对版本,不要盲目改代码。我吃过这个亏:在一个版本上调通的配置,换到另一个版本上生成出来少了一个关联属性,查了半天以为是元数据问题,最后发现是版本差异。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询