关联表数据更新:全删全增 还是 差异化更新?

232 阅读3分钟

关联表数据变更时的更新问题,在日常开发中很常见,比如 项目-项目文件、学生-学生奖项、试卷-试卷题目 等关联,就要涉及到关联数据更新的问题。

实现方法


我们一般有两种实现

  • 全删全增:删除原先的所有关联数据,再进行新增
  • 差异化更新:判断需要新增、修改、删除的数据,差异化更新

全删全增,实现是比较方便的,不需要进行复杂的判断。差异化更新实现起来就会麻烦点,需要比对出新增、更改、删除的具体数据。

以下是两种方式的对比:

对比项全部删除再新增差异化增删改
实现难度简单复杂,需要比对旧数据和新数据
性能小数据量下性能好,大数据量下性能差大数据量下更优,避免不必要的操作
数据准确性高,所有数据替换要小心比对逻辑,容易出错
审计追踪不好追踪变更历史(可能记录的是“全删全增”)可以精确记录“新增了什么、改了什么、删了什么”
数据库约束/触发器影响可能触发级联删除、外键约束更容易控制
常见使用场景前端传全部数据、变更频繁较小的数据集数据量大、需要精细控制或业务有变更日志要求

使用场景


实际场景下,考虑的因素比较复杂,如果单从数据量出发,

  • < 100 条:全删全增
  • >= 100 条:差异化更新

示例代码


全删全增

// 查询已有数据
List<T> existList = getList(relateId);

// 删除id列表
Set<Long> toDeleteIds = existList.stream()
    .map(T::getId)
    .filter(Objects::nonNull)
    .collect(Collectors.toSet());

// 删除全部
deleteBatch(toDeleteIds);
// 新增全部
addBatch(inputList);

差异化更新

用交集、差集的方式,会比较好理解一些

1751364924231.png

// 查询已有数据
List<T> existList = getList(relateId);

// 已存数据
Set<Long> existIds = existList.stream()
    .map(T::getId)
    .filter(Objects::nonNull)
    .collect(Collectors.toSet());

// 新输入数据
Set<Long> inputIds = inputList.stream()
    .map(T::getId)
    .filter(Objects::nonNull)
    .collect(Collectors.toSet());

// 交集:更新用
Set<Long> toUpdateIds = new HashSet<>(existIds);
toUpdateIds.retainAll(inputIds);
// 更新对象列表
List<T> toUpdate = inputList.stream()
    .filter(item -> toUpdateIds.contains(item.getId()))
    .collect(Collectors.toList());

// 差集1:删除用
Set<Long> toDeleteIds = new HashSet<>(existIds);
toDeleteIds.removeAll(inputIds);

// 差集2:新增用
List<T> toAdd = inputList.stream()
    .filter(item -> item.getId() == null)
    .collect(Collectors.toList());

// 更新
updateBatch(toUpdate);
// 删除
deleteBatch(toDeleteIds);
// 新增
addBatch(toAdd);

也可以基于此逻辑,进一步封装为工具方法

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class DiffUtils {

    public static <T, ID> DiffResult<T, ID> diff(
            List<T> existList,
            List<T> inputList,
            Function<T, ID> idGetter
    ) {
        // 去除 null
        List<T> existNotNull = Optional.ofNullable(existList)
            .orElse(Collections.emptyList());
        List<T> inputNotNull = Optional.ofNullable(inputList)
            .orElse(Collections.emptyList());

        // 构建 ID 集合
        Set<ID> existIds = existNotNull.stream()
                .map(idGetter)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Set<ID> inputIds = inputNotNull.stream()
                .map(idGetter)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        // 交集:用于更新
        Set<ID> toUpdateIds = new HashSet<>(existIds);
        toUpdateIds.retainAll(inputIds);

        // 差集1:数据库有但前端没有 -> 删除
        Set<ID> toDeleteIds = new HashSet<>(existIds);
        toDeleteIds.removeAll(inputIds);

        // 差集2:前端新增(id 为 null)
        List<T> toAdd = inputNotNull.stream()
                .filter(item -> idGetter.apply(item) == null)
                .collect(Collectors.toList());

        // 交集部分,更新
        List<T> toUpdate = inputNotNull.stream()
                .filter(item -> {
                    ID id = idGetter.apply(item);
                    return id != null && toUpdateIds.contains(id);
                })
                .collect(Collectors.toList());

        return new DiffResult<>(toAdd, toUpdate, new ArrayList<>(toDeleteIds));
    }

    public static class DiffResult<T, ID> {
        private final List<T> toAdd;
        private final List<T> toUpdate;
        private final List<ID> toDeleteIds;

        public DiffResult(List<T> toAdd, List<T> toUpdate, List<ID> toDeleteIds) {
            this.toAdd = toAdd;
            this.toUpdate = toUpdate;
            this.toDeleteIds = toDeleteIds;
        }

        public List<T> getToAdd() {
            return toAdd;
        }

        public List<T> getToUpdate() {
            return toUpdate;
        }

        public List<ID> getToDeleteIds() {
            return toDeleteIds;
        }
    }
}

使用工具方法

DiffResult<T, ID> diff = DiffUtils.diff(
    existList,
    inputList,
    T::getId
);

// 新增
addBatch(diff.getToAdd());
// 更新
updateBatch(diff.getToUpdate());
// 删除
deleteBatch(diff.getToDeleteIds());