You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring开发Redmine更新接口时如何仅修改请求传入的非空字段

解决方案

针对你提到的固定字段更新、空指针两个问题,有两种可落地的实现方案,你可以根据项目场景选择:

方案1:类型安全的函数式工具封装(推荐)

该方案无需反射,编译期可校验逻辑,排查问题成本低,30个字段仅需各加一行配置即可。
首先封装通用的条件赋值工具方法:

/**
 * @param json 请求体
 * @param fieldName 请求字段名
 * @param setter Issue对应属性的setter方法引用
 * @param converter 请求值到属性类型的转换器
 */
private <T> void setIfPresent(ObjectNode json, String fieldName, Consumer<T> setter, Function<String, T> converter) {
    // 先判断字段是否存在、不为空,再取值,从根源避免空指针
    if (json.has(fieldName) && !json.get(fieldName).isNull()) {
        T value = converter.apply(json.get(fieldName).asText());
        setter.accept(value);
    }
}

改造原有更新逻辑:

@RequestMapping(value = "/issues/update/{id}", method = RequestMethod.PUT)
public ResponseEntity<Object> issueUpdate(@RequestBody ObjectNode json, @PathVariable int id) throws RedmineException {
    String apiKey = json.get("API_KEY").asText();
    RedmineManager mgr = RedmineManagerFactory.createWithApiKey("http://localhost:3001/", apiKey);
    IssueManager issueManager = mgr.getIssueManager();
    Issue issue = issueManager.getIssueById(id);

    // 所有字段仅需一行配置,未传的字段自动保留原值
    setIfPresent(json, "SUBJECT", issue::setSubject, Function.identity());
    setIfPresent(json, "DESCRIPTION", issue::setDescription, Function.identity());
    setIfPresent(json, "ASSIGNEE_ID", issue::setAssigneeId, Integer::valueOf);
    setIfPresent(json, "PRIORITY_ID", issue::setPriorityId, Integer::valueOf);
    // 剩余20+字段按照上面的格式依次添加即可

    issue.update();
    return new ResponseEntity<>(HttpStatus.OK);
}

方案2:全自动反射实现

如果不想逐个配置字段,可以用反射自动映射JSON字段和Issue属性,仅需维护一次字段映射关系即可:

// 配置JSON请求字段和Issue类属性的映射关系,禁止更新的字段直接不配置即可
private static final Map<String, String> FIELD_MAPPING = Map.ofEntries(
    Map.entry("SUBJECT", "subject"),
    Map.entry("DESCRIPTION", "description"),
    Map.entry("ASSIGNEE_ID", "assigneeId"),
    Map.entry("PRIORITY_ID", "priorityId"),
    // 其他所有字段一次性配置完成
);

private void fillIssueFromJson(Issue issue, ObjectNode json) throws Exception {
    Iterator<String> reqFields = json.fieldNames();
    while (reqFields.hasNext()) {
        String reqField = reqFields.next();
        // 跳过未配置的字段、空值字段
        if (!FIELD_MAPPING.containsKey(reqField) || json.get(reqField).isNull()) {
            continue;
        }
        String javaField = FIELD_MAPPING.get(reqField);
        // 构造setter方法名
        String setterName = "set" + Character.toUpperCase(javaField.charAt(0)) + javaField.substring(1);
        Field field = Issue.class.getDeclaredField(javaField);
        Method setter = Issue.class.getMethod(setterName, field.getType());

        // 按字段类型做值转换
        Object value;
        Class<?> fieldType = field.getType();
        if (fieldType == String.class) {
            value = json.get(reqField).asText();
        } else if (fieldType == Integer.class || fieldType == int.class) {
            value = json.get(reqField).asInt();
        } else if (fieldType == Long.class || fieldType == long.class) {
            value = json.get(reqField).asLong();
        } else if (fieldType == Date.class) {
            value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(json.get(reqField).asText());
        } else {
            // 其他自定义类型自行扩展转换逻辑
            continue;
        }
        setter.invoke(issue, value);
    }
}

调用时仅需一行即可完成所有字段的赋值:

Issue issue = issueManager.getIssueById(id);
fillIssueFromJson(issue, json);
issue.update();

注意事项

  • 所有取值逻辑必须先判断json.has(fieldName),避免json.get()返回null触发空指针
  • 反射方案需要覆盖所有用到的字段类型转换逻辑,避免类型转换异常
  • 不允许修改的系统字段(如创建时间、创建人ID)不要加入字段映射表,避免被非法更新

内容的提问来源于stack exchange,提问作者George

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 21:45:03