DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
晚安云;香港 CN2 ¥15/月
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS

MapStruct 1.7.0.Beta2 映射 Optional 集合报 No value present

同一个 mapper 中标量有 isPresent 检查,Optional<Map> 或 Optional<List> 却直接 get。定位生成代码,用明确的创建映射保留空值语义。

MapStruct 生成的 mapper 中,Optional<String> 有 isPresent() 判断,旁边的 Optional<Map<...>>、Optional<List<...>> 却直接调用 get()。输入为 Optional.empty() 时,运行到生成类就失败:

java.util.NoSuchElementException: No value present

这条已确认路径针对 MapStruct 1.7.0.Beta2。它是预发布版本中的集合映射问题,不能据此断言所有 MapStruct 版本都不支持 Optional,也不能把正常的空 Optional 误当成非法输入。

先打开生成代码,找到缺失的判断

检查实际注解处理器生成的实现,而不仅是接口。问题代码可能是:

if (source.getName().isPresent()) {
    name = source.getName().get();
}
attributes = source.getAttributes().get();
tags = source.getTags().get();

三项 getter 都返回 Optional,但只有标量经过 presence check。集合分支若先把 get() 的结果存入局部变量,再判断局部变量是否为 null,已经太晚;异常会在赋值时抛出。

同样核对 mapstruct 与 mapstruct-processor 的实际版本。只换注解依赖、生成实现仍来自另一个处理器,不能判断修复状态。给 mapper 加 NullValueCheckStrategy.ALWAYS 或调整空 Map 策略,也不保证补回这个解包前判断。

对明确的创建映射,暂时写出解包规则

对于这里的“新建目标对象”场景,可以将出问题的方法实现为显式映射,让 Optional.empty 的去向由业务决定。以下片段放在已有 mapper 接口中,假定 Source 有三个对应 getter,目标是 record Target(String name, Map<String, String> attributes, List<String> tags),且 getter 返回的 Optional 容器本身非 null:

default Target toTarget(Source source) {
    if (source == null) {
        return null;
    }
    return new Target(
        source.getName().orElse(null),
        source.getAttributes().orElse(null),
        source.getTags().orElse(null)
    );
}

这段方法将空 Optional 映射为目标 null,将 Optional.of(emptyMap) 或 Optional.of(emptyList) 保留为空集合。它不是把所有缺省情况都替换成空集合;若目标模型不允许 null,需要另行选择符合接口契约的默认值。

上述代码直接沿用传入集合的引用,没有自动复制或深拷贝。如果原生成映射负责复制,或者目标不能共享可变集合,应在这个边界显式恢复同样的复制规则。不能为了消掉异常悄悄改变对象隔离。

容器本身为 null 是另一种输入,需要在模型构造时约束,或在显式映射里另设分支。这里不建议把它和 Optional.empty 无条件合并,尤其是更新接口可能用它们分别表示“保持原值”和“清空”。

更新映射不能照搬创建映射

使用 @MappingTarget 的 PATCH 场景,要先列清 null、Optional.empty、Optional.of(value) 的业务含义。若 null 表示不修改,empty 表示清空,统一用 isPresent() 跳过就会丢掉清空操作;统一 orElse(null) 写回又会覆盖本应保留的值。

因此这份临时实现仅替换明确的创建方法,不是修改所有 mapper 的全局策略。若项目选择离开 Beta2,也应先检查所需 Optional 功能在目标版本中的支持情况,不能只把版本降回去就期待相同生成代码。

验收从全空、全部有值、Optional 包着空集合三种输入开始,另检查目标是否需要集合副本与 source 为 null 的约定。确认实际调用已走新的方法,不再运行旧生成类中的无条件 get()。以后切回自动映射前,重新审查所选正式版本生成的实现及相同样例,不以问题状态或预发布补丁代替验收。