ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kettle作业循环获取结果集:JavaScript变量驱动转换传参实战

Kettle作业循环获取结果集:JavaScript变量驱动转换传参实战 简介面向KettlePDI数据集成开发者这份PDF资料完整演示了作业中循环获取结果集并传入转换处理的实现方法特别适合需要批量遍历表名或结果集后逐一处理的场景。整个案例由作业j1.kjb串联t1.ktr与var.ktr两个转换前端转换负责生成结果集后端转换负责接收变量并输出。核心逻辑集中在JavaScript步骤中——首先调用previous_result.getRows()读取上级结果集并对结果集是否为空或行数为0做出判断随后利用parent_job.setVariable()将表名列表以数组形式存入变量tables同时保存总行数size和循环控制变量i每当循环开始时脚本通过prevRow.get(i)读取当前行的id与name更新作业变量再判断i加1后是否小于size以决定继续遍历还是结束循环。var.ktr接收这些变量后通过文本文件输出步骤将结果写入本地txt文件完成整个数据处理链路。资源包为1个PDF文件、大小约130KB内容精炼、步骤清晰既有完整代码片段也有转换配置说明适合有一定Kettle基础、希望掌握结果集循环调度技巧与作业间变量传递的开发人员。该资料已有3113人学习读者可借此快速理解常见空值判断、循环控制逻辑以及作业与转换如何协同工作并迁移到自己的数据集成项目中。1. Kettle循环获取结果集这个需求为什么比想象中麻烦做过 Kettle 数据集成的人大概率遇到过这个场景上游一个转换跑出了几十行结果每一行都要作为参数传给下一个转换逐条处理并输出。很多人第一反应是用「复制行到结果」再连接两个转换结果发现转换之间根本不共享数据流第二个转换拿到的是空结果。这套资源给的是另一种思路用作业Job里的 JavaScript 步骤读previous_result.getRows()把结果集拆成作业变量再靠变量循环驱动后面的转换。核心就三件事——拿结果集、写变量、控制循环条件。适合正在做批量表抽取、动态参数传递的 ETL 开发看完能直接照着搭也能避开我在复现过程中踩过的几个硬坑。2. 先理清数据流向结果集、作业变量与转换之间的协作关系2.1 转换和作业的角色差异为什么转换之间不能直接传结果集Kettle 里有两个容易混淆的概念转换Transformation和作业Job。转换里跑的是数据流——从输入步骤到输出步骤数据以行集RowSet的形式在步骤之间流动。而作业是调度器负责按顺序执行转换、校验、脚本等条目。两者的本质区别在于转换里步骤之间可以用连线传输数据但两个独立的转换文件之间没有数据流通道。这套资源里的结构是「作业 → 转换 t1.ktr → 作业内 JavaScript → 转换 var.ktr」。t1.ktr 跑完后的结果集并不会自动流到 var.ktr。真正把数据桥接过来的是作业里那两个 JavaScript 步骤previous_result.getRows()拿到结果parent_job.setVariable()把字段放进作业变量池var.ktr 再从变量池取出来用。所以整条链路的关键不是转换本身而是作业里的 JavaScript 步骤做了「数据拆箱」的动作。2.2 previous_result.getRows() 拿到的是什么形态的数据previous_result是 Kettle 作业 JavaScript 步骤里的内置对象代表上一个作业条目产生的结果。注意这里说的是「作业条目」不是普通转换步骤。只有当上一个条目是转换、并且转换里配置了「把执行结果写到日志或作为结果返回」时previous_result里才有数据。调用getRows()返回的是一个 List里面每个元素是一行结果可以通过getString(字段名)或getObject(字段名)取值。常见误用是把getRows()当成数据库 ResultSet 一样来遍历。实际上它就是一个内存中的数组所以你可以先读size()拿到行数再按下标 0、1、2… 逐个取字段。这个特性决定了循环的写法——先取总量再逐行移动下标。下面这段就是这套资源 j1.kjb 里的核心逻辑var prevRow previous_result.getRows(); // 获取上一个作业条目返回的结果集 if (prevRow null (prevRow.size() 0)) { false; // 结果集为空时作业条目返回 false作业走失败分支 } else { parent_job.setVariable(tables, prevRow); // 把整个结果集暂存到变量 tables 中 parent_job.setVariable(size, prevRow.size()); // 记录总行数作为循环上限 parent_job.setVariable(i, 0); // 初始化循环下标 parent_job.setVariable(id, prevRow.get(0).getString(id, )); parent_job.setVariable(name, prevRow.get(0).getString(name, )); true; // 作业条目返回 true作业继续走成功分支 }这段代码里有一个很隐蔽的坑prevRow null (prevRow.size() 0)这个条件如果prevRow真的为 null执行prevRow.size()会直接抛空指针异常。正确写法应该是prevRow null || prevRow.size() 0。原资源里的这个笔误我后面会在避坑章节展开讲。从逻辑上说这里的意图是「结果集为空就终止非空就继续」但短路判断写错了运算符。2.3 作业变量是循环传参的「总线」选型理由在这里为什么不直接在转换里用「设置变量」步骤因为转换里的变量作用域是当前转换作业条目执行完毕、变量生命周期结束下一个转换就找不到了。而parent_job.setVariable()写入的是作业级变量整个作业运行期间都存活所有子转换都能通过${变量名}引用。这也是这套方案最值得借鉴的地方用作业变量池做数据中转避免在转换之间建数据库临时表或写中间文件。对于一个跑批任务来说减少 IO 就意味着减少故障点。代价是变量名的管理变得重要——后面你会看到变量名写错、类型不一致、作用域重叠都可能导致数据静默丢失或循环提前终止。3. 实战复现从 t1.ktr 生成结果集到 var.ktr 输出 txt 的完整链路3.1 第一步搭好 t1.ktr让它返回一个「可被识别」的结果集在作业里跑转换并期望previous_result.getRows()能拿到数据前提是 t1.ktr 的「作业条目」配置里勾选了返回结果字段。实际操作中我在 t1.ktr 末尾加了一个「复制行到结果」步骤Copy rows to result这个步骤的作用就是把你当前的数据行集「导出」为作业可识别的结果集。如果缺少这一步即使转换跑成功了previous_result.getRows()拿到的也是空 List因为 Kettle 根本不知道要把哪些行作为结果返回。t1.ktr 的典型结构是表输入Table Input→ 字段选择Select Values→ 复制行到结果Copy rows to result。表输入里写你要抽取的 SQL字段选择保证只有id、name这两个业务字段被传递减少内存消耗。复制行到结果这个步骤不需要额外配置它会自动把上游行集复制一份给作业。这一步有一个值得注意的细节如果 t1.ktr 里面用了「并行」或「多线程」的执行方式结果集的顺序可能不稳定。getRows()拿到的 List 顺序取决于转换里数据到达复制步骤的顺序。所以如果后续的循环处理对顺序敏感必须在 t1.ktr 里用「排序」步骤提前排好序。我一般会在表输入 SQL 里直接用 ORDER BY 排好避免在转换里多跑一个排序步骤。3.2 第二步j1.kjb 里的 JavaScript 循环控制逐行拆解这套资源里最核心的部分就是 job 里的两个 JavaScript 步骤。第一个步骤负责初始化和取第一行数据第二个步骤负责「移动下标并判断是否继续循环」。把两个步骤交替放在作业的循环回路里就实现了对结果集的逐行遍历。先看第一个 JavaScript 步骤初始化var prevRow previous_result.getRows(); // 修复了原资源的逻辑错误用 || 替代 // 用长度判断空结果集而不是直接比较 size if (prevRow null || prevRow.size() 0) { false; // 返回 false作业走失败分支结束执行 } else { parent_job.setVariable(tables, prevRow); // 保存整个结果集供第二个脚本继续读取 parent_job.setVariable(size, prevRow.size()); // 循环总次数 parent_job.setVariable(i, 0); // 当前循环下标从 0 开始 parent_job.setVariable(id, prevRow.get(0).getString(id, )); // 取第一行的 id parent_job.setVariable(name, prevRow.get(0).getString(name, )); // 取第一行的 name true; // 返回 true作业走成功分支进入循环体 }这里有个设计上的精妙之处parent_job.setVariable(tables, prevRow)把整个 List 存进了变量。这意味着第二个 JavaScript 步骤并不需要重新调用previous_result.getRows()而是直接从变量tables里取之前的 List。这样处理的好处是第二个脚本可以独立地在循环体运行不必依赖上一个作业条目仍然持有结果集。第二个 JavaScript 步骤循环控制var prevRow previous_result.getRows(); // 实际使用中更稳妥的做法是从变量里取 List而不是再次调 getRows() // 因为循环体里上一个条目可能是 var.ktr 转换它不一定有结果返回 var tables parent_job.getVariable(tables); var size new Number(parent_job.getVariable(size)); var i new Number(parent_job.getVariable(i)) 1; if (i size) { parent_job.setVariable(id, tables.get(i).getString(id, )); parent_job.setVariable(name, tables.get(i).getString(name, )); parent_job.setVariable(i, i); true; // 还有下一行继续循环 } else { false; // 已经读完最后一行终止循环 }这里我把原资源的prevRow.get(i)换成了tables.get(i)原因在于当循环体里的 var.ktr 执行完毕返回后作业会回到这个 JavaScript 步骤继续判断。但此时「上一个作业条目」是 var.ktr它如果不返回结果集previous_result.getRows()拿到的是空——于是你永远无法继续循环。把 List 存在变量里第二个脚本就不依赖 previous_result 了这是一个实战中非常重要的改动。两个步骤之间的流转顺序是初始化脚本返回 true → 作业执行 var.ktr → var.ktr 处理完当前 id/name → 回到循环控制脚本 →i1并判断是否还有下一行 → 有则返回 true 再执行 var.ktr无则返回 false 结束。这个「作业条目回环」的模式是 Kettle 实现循环的标准姿势。3.3 第三步var.ktr 接收变量写出 txt 文件var.ktr 是一个普通转换它通过${id}、${name}的方式引用作业变量。作业变量默认是全局的子转换里直接用变量表达式即可。var.ktr 的典型结构是「生成记录」步骤生成一行数据便于后面输出。「获取变量」步骤Get Variables把${id}、${name}映射为转换内的字段。这一步有两种做法一是用表单输入字段名二是直接在后续步骤的 SQL 或文件路径里写${id}。「文本文件输出」步骤配置输出路径比如D:/kettle_output/${name}.txt这样每条数据就能分别落到不同文件里。这里有一个经验不要在文本文件输出里直接写死文件名把它写成带变量名路径循环的价值才能体现出来。我用的是下面这种方式文件名称: /data/output/${name}.txt 扩展名: txt 编码: UTF-8 字段: id, name每次循环var.ktr 执行一次${name} 不同输出文件就不同。如果只是想把所有行追加到同一个文件则把文件名称写死并在「文本文件输出」步骤里勾选「追加」模式但注意多线程执行转换时追加文件可能出现行序颠倒的问题所以更稳妥的做法是每次循环写独立文件。3.4 运行方式在命令行里直接启动整套作业配置完成后整套逻辑是用作业Job驱动的运行入口在 j1.kjb 所在的作业文件上。命令行运行方式./pan.sh -file/opt/kettle/jobs/j1.kjb \ -levelBasic \ -logfile/opt/kettle/logs/run.log如果是在 Windows 环境对应执行pan.bat。这里用-levelBasic是为了在日志里看到每个作业条目的执行结果和变量变化。调试循环时我更习惯用-levelDetailed它会输出 JavaScript 步骤里的每一条parent_job.setVariable()调用记录能直接看出每次循环到底传了什么值进去。生产环境我个人会降到 Basic因为 Detailed 级别日志量太大跑批任务会撑爆磁盘。4. 循环传参的常见坑与排查变量被覆盖、空结果集与数组越界4.1 坑一空结果集判断写错运算符作业直接报空指针现象t1.ktr 正常跑完但查询结果为空作业在 JavaScript 步骤报错Cannot call method size of null而不是正常走失败分支。原因原资源代码里的判断条件是prevRow null (prevRow.size() 0)。当 prevRow 为 null 时左边为真但 JavaScript 仍然会继续执行右边表达式来求值于是prevRow.size()直接抛异常。运算符用错短路失效空结果集场景完全失去保护。解决改成if (prevRow null || prevRow.size() 0)用||让 null 判断生效后面不再执行。if (prevRow null || prevRow.size() 0) { false; } else { // 正常逻辑 }从那以后我在 Kettle 里写任何null判断都会下意识检查是不是用了||而不是。这个坑太隐蔽了尤其在你处理的数据源偶尔会出现空表时。4.2 坑二getString() 遇到空值变量变成字符串 null现象源表里的name字段允许 NULL循环执行时发现输出的 txt 里有字面量 null 而不是空值。原因归因于getString()的默认值处理。原资源里parent_job.setVariable(name, prevRow.get(0).getString(name, ))第二个参数是默认值但有的 Kettle 版本里getString()的默认值只在字段为空字符串时生效字段本身为 NULL 时返回的可能是字符串 null。解决在 t1.ktr 的「字段选择」步骤里对name字段做空值替换比如把 NULL 替换成空串再进入结果集。另一个方案是在 JavaScript 里手动处理var nameVal prevRow.get(0).getString(name, ); if (nameVal null || nameVal null) { nameVal ; } parent_job.setVariable(name, nameVal);这种「字段类型和数据质量」的问题在 Kettle 里特别常见尤其当数据源是 MySQL 且字段允许 NULL 时。建议把空值处理的逻辑统一放在 t1.ktr 里不要在多个转换里各做各的否则排查极其痛苦。4.3 坑三循环变量 i 被重复初始化作业陷入死循环现象作业在第二次循环时跳回第一个 JavaScript 步骤而不是到第二个循环控制步骤导致i永远为 0反复执行第一个转换和初始化脚本。原因作业里两个 JavaScript 步骤的连接顺序配置错误。第一个 JavaScript 步骤返回 true 后需要连接到循环控制脚本然后再连接到 var.ktr最后跳回循环控制脚本形成回路。但很多新手会把 true 分支直接连回 t1.ktr导致每次循环都重新执行「获取结果集 初始化下标」i被重置size恒大于i死循环。解决作业连线一定要按「初始化 → var.ktr → 循环控制 → 判断true → var.ktr / false → 结束」的结构走。检查作业图上是否有指向 t1.ktr 的回边如果有删掉即可。我建议在作业里把两个 JavaScript 步骤分别命名为「初始化_读取首行」和「循环体_判断递增」名字带语义半个月后回来看也知道连线逻辑是什么。4.4 坑四作业变量在转换里取不到值${id} 变成空串现象var.ktr 能执行但「获取变量」步骤里id、name的值是空的txt 文件内容只有分隔符。原因作业变量作用域覆盖子转换但转换里「获取变量」步骤存在缓存。如果 var.ktr 之前被单独运行过Kettle 有时会缓存变量引用另一个更常见的原因是 var.ktr 里「获取变量」步骤的变量名拼写不一致比如脚本里写的是id转换里引用的是${ID}大小写不匹配。解决打开 var.ktr 的「获取变量」步骤清空缓存并重新映射字段名统一所有变量名的大小写建议全程小写。另外我习惯在 var.ktr 里加一个「写日志」步骤观察变量是否成功传入id ${id}, name ${name}用日志确认无误后再接输出步骤能少走很多弯路。5. 验证这套循环逻辑把 getRows 的结果变成可见的执行痕迹完全照搬这套作业后我强烈建议你先不要急着接正式输出先在循环体里放一个「写日志」步骤把每次循环的id和name打印出来确认循环次数和数据分布都对再接 txt 输出。这是整个流程里性价比最高的验证动作。写日志步骤的配置很简单在 var.ktr 最前面加一个「写日志」步骤日志级别选 Basic日志内容写当前处理 id${id}, name${name}, 循环下标${i}, 总行数${size}然后以-levelBasic运行作业你会看到每次循环对应一条日志。如果日志里 i 从 0 一直走到 size-1说明循环控制和变量传递都正常。如果日志里 i 根本不递增或者 id 是同一个值就回头检查 4.3 里说的连线问题——大多数死循环都能通过这种方式快速定位。另一个值得做的验证是把结果集 List 直接以文本形式打到日志里。在第一个 JavaScript 步骤里加一句var logText ; for (var j 0; j prevRow.size(); j) { logText prevRow.get(j).getString(id, ) ,; }这样可以直观看到整个结果集的宽度。如果遇到转换传参信息不对称这个日志能帮你快速判断是 t1.ktr 结果集少字段还是 var.ktr 变量映射写错了。这套循环思路还能顺手扩展出两个实用变体。第一个变体是处理多字段传参不局限于 id、name把结果集的字段全部动态取出存成逗号分隔的字符串var.ktr 里用「字符串拆分为字段」还原。第二个变体是把循环范围接到外部配置表t1.ktr 从配置表读出一批「处理规则」每条规则驱动一次转换构造成自动化的调度中心。从这套基础结构出发无论是批量文件分发、多表增量抽取还是参数驱动跑批都能套用同一套作业回环的逻辑。从那以后我每次搭 Kettle 循环作业都会强制走一遍「空结果集检查 → 循环变量打印 → 结束条件验证」三步这套动作帮我在正式跑批前拦截了至少五六次配置错误。希望帮到你。本文还有配套的精品资源点击获取
返回列表