{"record":{"id":"ab61012fefe3ea10","repo":"Tencent/APIJSON","slug":"list-join-join-onlist-getoriginkey","errorCode":null,"errorMessage":"服务器内部错误，List<Join> 中 Join.onList[{}].getOriginKey() = null！","messagePattern":"服务器内部错误，List<Join> 中 Join\\.onList\\[(.+?)\\]\\.getOriginKey\\(\\) = null！","errorType":"exception","errorClass":"NullPointerException","httpStatus":500,"severity":"critical","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java","lineNumber":611,"sourceCode":"\t\t\t\t\t\t\tcolumnIndexAndJoinMap[i - 1] = curJoin;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// 如果是主表则直接用主表对应的 item，否则缓存副表数据到 childMap\r\n\t\t\t\t\t\tJoin prevJoin = columnIndexAndJoinMap == null || i < 2 ? null : columnIndexAndJoinMap[i - 2];\r\n\t\t\t\t\t\tif (curJoin != prevJoin) {  // 前后字段不在同一个表对象，即便后面出现 null，也不该是主表数据，而是逻辑 bug 导致\r\n\t\t\t\t\t\t\tSQLConfig<T, M, L> viceConfig = curJoin != null && curJoin.isSQLJoin() ? curJoin.getCacheConfig() : null;\r\n\t\t\t\t\t\t\tboolean hasPK = false;\r\n\t\t\t\t\t\t\tif (viceConfig != null) {  //FIXME 只有和主表关联才能用 item，否则应该从 childMap 查其它副表数据\r\n\t\t\t\t\t\t\t\tList<On> onList = curJoin.getOnList();\r\n\t\t\t\t\t\t\t\tint size = onList == null ? 0 : onList.size();\r\n\t\t\t\t\t\t\t\tif (size > 0) {\r\n\t\t\t\t\t\t\t\t\tString idKey = viceConfig.getIdKey();\r\n\t\t\t\t\t\t\t\t\tString tblKey = config.gainTableKey();\r\n\t\t\t\t\t\t\t\t\tfor (int j = size - 1; j >= 0; j--) {\r\n\t\t\t\t\t\t\t\t\t\tOn on = onList.get(j);\r\n\t\t\t\t\t\t\t\t\t\tString ok = on == null ? null : on.getOriginKey();\r\n\t\t\t\t\t\t\t\t\t\tif (ok == null) {\r\n\t\t\t\t\t\t\t\t\t\t\tthrow new NullPointerException(\"服务器内部错误，List<Join> 中 Join.onList[\" + j + (on == null ? \"] = null！\" : \".getOriginKey() = null！\"));\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t\t\t\tString k = ok.substring(0, ok.length() - 1);\r\n\t\t\t\t\t\t\t\t\t\tString ttk = on.getTargetTableKey();\r\n\r\n\t\t\t\t\t\t\t\t\t\tM target = StringUtil.equals(ttk, tblKey) ? item : (viceItem == null ? null : JSON.get(viceItem, ttk));\r\n\t\t\t\t\t\t\t\t\t\tObject v = target == null ? null : target.get(on.getTargetKey());\r\n\t\t\t\t\t\t\t\t\t\thasPK = hasPK || (k.equals(idKey) && v != null);\r\n\r\n\t\t\t\t\t\t\t\t\t\tviceConfig.putWhere(k, v, true);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tif (viceConfig == null) { // StringUtil.isEmpty(viceSql, true)) {\r\n\t\t\t\t\t\t\t\tLog.i(TAG, \"execute viceConfig == null >> item = null; >> \");\r\n\t\t\t\t\t\t\t\tcurItem = null;\r\n\t\t\t\t\t\t\t}\r","sourceCodeStart":593,"sourceCodeEnd":629,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java#L593-L629","documentation":"Internal NullPointerException: a non-null On object in join.getOnList() returns null from getOriginKey(). The executor needs originKey (e.g. \"id@/User/id\") to derive the vice-table where key; null means the On was built without its origin key, breaking the JOIN cache lookup.","triggerScenarios":"Programmatically created On objects where setOriginKey was never called, or joiner-string parsing that failed to capture the origin key portion but still added the On.","commonSituations":"Custom extensions constructing On instances; reflective/deserialized Join configurations missing fields; version skew in On's constructor signature.","solutions":["Always set originKey when building On in code (it encodes key + '/' + target path).","Check the joiner string segment corresponding to the reported index for a missing '/key@/...' part.","Match versions of APIJSON jars; if input looks correct, file an issue with the request body."],"exampleFix":"// before\nOn on = new On(); on.setKey(\"userId\");\n// after\nOn on = new On(); on.setKey(\"userId\"); on.setOriginKey(\"userId@/User/id\");","handlingStrategy":"try-catch","validationCode":"if (join != null && join.getOnList() != null) {\n  for (int i = 0; i < join.getOnList().size(); i++) {\n    On on = join.getOnList().get(i);\n    if (on != null && on.getOriginKey() == null) throw new IllegalStateException(\"On[\" + i + \"] missing originKey\");\n  }\n}","typeGuard":null,"tryCatchPattern":"catch (NullPointerException npe) { if (npe.getMessage() != null && npe.getMessage().contains(\"getOriginKey() = null\")) { /* rebuild join from well-formed joiner string */ } }","preventionTips":["Always set originKey when constructing On programmatically.","Prefer parsed joiner strings over manual On construction."],"tags":["apijson","join","internal-error","null-pointer"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}