You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Protobuf 3 Java:如何按需更新字段并实现通用简洁的代码?

通用化优化Protobuf增量更新代码方案

这个场景我做API开发时经常碰到,硬编码每个字段的判断不仅繁琐,后续新增字段还得反复修改业务代码,完全可以改成通用化实现,下面分两种方案给你参考:

一、自定义通用反射工具类(推荐,精准控制)

Protobuf生成的所有Message类都实现了Message接口,我们可以利用它的反射API(Descriptor、FieldDescriptor)来写一个通用的增量合并方法,不管是什么类型的Protobuf对象都能复用。

1. 编写通用合并工具类

import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class ProtobufMergeUtils {

    // 通用增量合并方法:仅合并输入中非空且有变更的字段,排除指定字段
    public static <T extends Message> MergeResult<T> mergeIncremental(T existing, T input, Set<String> excludedFieldNames) {
        Builder builder = existing.toBuilder();
        boolean hasChange = false;

        Descriptors.Descriptor descriptor = existing.getDescriptorForType();
        // 遍历所有字段
        for (Descriptors.FieldDescriptor field : descriptor.getFields()) {
            // 跳过排除字段,以及输入中未显式设置的字段(用hasField判断,避免默认值干扰)
            if (excludedFieldNames.contains(field.getName()) || !input.hasField(field)) {
                continue;
            }

            Object inputValue = input.getField(field);
            Object existingValue = existing.getField(field);

            // 字段值不同则更新
            if (!Objects.equals(inputValue, existingValue)) {
                builder.setField(field, inputValue);
                hasChange = true;
            }
        }

        @SuppressWarnings("unchecked")
        T merged = (T) builder.build();
        return new MergeResult<>(merged, hasChange);
    }

    // 封装合并结果和变更标记
    public static class MergeResult<T extends Message> {
        private final T mergedMessage;
        private final boolean hasChange;

        public MergeResult(T mergedMessage, boolean hasChange) {
            this.mergedMessage = mergedMessage;
            this.hasChange = hasChange;
        }

        public T getMergedMessage() {
            return mergedMessage;
        }

        public boolean hasChange() {
            return hasChange;
        }
    }
}

2. 修改原有save方法

现在只需要调用通用方法,不用再硬编码每个字段:

public Model.Person save(Model.Person in) {
    String updateId = in.getId();
    System.out.println("updateId = " + updateId);
    Model.Person pExisting = get(updateId);

    // 定义不参与更新的"仅输出"字段
    Set<String> excludedFields = new HashSet<>();
    excludedFields.add("id");
    excludedFields.add("createTime");
    excludedFields.add("updateTime");

    ProtobufMergeUtils.MergeResult<Model.Person> mergeResult = ProtobufMergeUtils.mergeIncremental(pExisting, in, excludedFields);

    if (!mergeResult.hasChange()) {
        System.out.println("Nothing to update ... "+System.currentTimeMillis() );
        return pExisting;
    }

    final Model.Person updatePerson = mergeResult.getMergedMessage();
    map.put(updateId, updatePerson);
    //TODO 后续迁移至H2或PostgreSQL
    return updatePerson;
}

二、利用官方工具类protobuf-java-util

Google官方提供的protobuf-java-util库中有MergeUtil工具,可以简化合并逻辑,但需要额外处理字段排除:

1. 添加依赖(如果还没加)

<!-- Maven示例 -->
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java-util</artifactId>
    <version>你的Protobuf版本</version>
</dependency>

2. 实现合并逻辑

import com.google.protobuf.util.MergeUtil;
import com.google.protobuf.util.MergeOptions;

public Model.Person save(Model.Person in) {
    String updateId = in.getId();
    System.out.println("updateId = " + updateId);
    Model.Person pExisting = get(updateId);

    // 先过滤输入:清除不需要更新的字段
    Model.Person filteredInput = in.toBuilder()
        .clearId()
        .clearCreateTime()
        .clearUpdateTime()
        .build();

    // 配置合并选项:仅合并输入中存在的字段
    MergeOptions options = MergeOptions.newBuilder()
        .setMergeMissingFields(false)
        .build();

    Model.Person.Builder mergedBuilder = pExisting.toBuilder();
    try {
        MergeUtil.merge(filteredInput, mergedBuilder, options);
    } catch (Exception e) {
        // 处理合并异常
        throw new RuntimeException("Failed to merge person data", e);
    }
    Model.Person updatePerson = mergedBuilder.build();

    // 判断是否有变更
    if (updatePerson.equals(pExisting)) {
        System.out.println("Nothing to update ... "+System.currentTimeMillis() );
        return pExisting;
    }

    map.put(updateId, updatePerson);
    //TODO 后续迁移至H2或PostgreSQL
    return updatePerson;
}

方案对比

  • 自定义反射工具:灵活性更高,完全控制字段过滤和变更判断逻辑,新增字段时不需要修改业务代码,只需要确保排除字段集合正确即可。
  • 官方MergeUtil:代码更简洁,但字段排除需要手动清除输入字段,适合字段较少、逻辑简单的场景。

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

火山引擎 最新活动