关联表数据变更时的更新问题,在日常开发中很常见,比如 项目-项目文件、学生-学生奖项、试卷-试卷题目 等关联,就要涉及到关联数据更新的问题。
实现方法
我们一般有两种实现
- 全删全增:删除原先的所有关联数据,再进行新增
- 差异化更新:判断需要新增、修改、删除的数据,差异化更新
全删全增,实现是比较方便的,不需要进行复杂的判断。差异化更新实现起来就会麻烦点,需要比对出新增、更改、删除的具体数据。
以下是两种方式的对比:
| 对比项 | 全部删除再新增 | 差异化增删改 |
|---|---|---|
| 实现难度 | 简单 | 复杂,需要比对旧数据和新数据 |
| 性能 | 小数据量下性能好,大数据量下性能差 | 大数据量下更优,避免不必要的操作 |
| 数据准确性 | 高,所有数据替换 | 要小心比对逻辑,容易出错 |
| 审计追踪 | 不好追踪变更历史(可能记录的是“全删全增”) | 可以精确记录“新增了什么、改了什么、删了什么” |
| 数据库约束/触发器影响 | 可能触发级联删除、外键约束 | 更容易控制 |
| 常见使用场景 | 前端传全部数据、变更频繁较小的数据集 | 数据量大、需要精细控制或业务有变更日志要求 |
使用场景
实际场景下,考虑的因素比较复杂,如果单从数据量出发,
< 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);
差异化更新
用交集、差集的方式,会比较好理解一些
// 查询已有数据
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());