集合差异工具类-DiffWrapper
原因
在编辑过程中,肯定会存在对于子表的更新操作,这种更新分为三种: 要加的, 要删的,要更新的,并且传参只有一个modifyVO的, 每一个都写有点过于冗余,故考虑提取一个工具类
流程图思路
流程图对应代码如下:
graph TD
A[输入] --> B[空集合处理]
B --> C[提取旧数据ID集合]
B --> D[提取新数据ID集合]
C --> E[计算删除ID集合]
D --> F[识别新增项]
D --> G[识别修改项]
E --> H[构建结果]
F --> H
G --> H
代码如下:
@Setter
@Getter
@Slf4j
public class DiffWrapper<T, ID> {@ApiModelProperty("要添加的集合列表")private List<T> toAddList;@ApiModelProperty("要删除的ID集合列表")private List<ID> toRemoveIdList;@ApiModelProperty("要修改的列表")private List<T> toModifyList;/*** 创建差异计算结果* * @param dbItemList 数据库老数据* @param newList 前端入参新数据* @param idExtractor ID提取函数,基于那个字段进行判断* @param fieldSelectors 可变参数指定需要比较的字段 , equal比较走的是 Objects.equals* @return 差异包装结果*/@SafeVarargspublic static <T, ID> DiffWrapper<T, ID> newInstance(List<T> dbItemList, List<T> newList, Function<T, ID> idExtractor, Function<T, ?>... fieldSelectors) {// 空集合防御List<T> safeOlds = Optional.ofNullable(dbItemList).orElse(Collections.emptyList());List<T> safeNews = Optional.ofNullable(newList).orElse(Collections.emptyList());// 提取ID集合Set<ID> oldIds = safeOlds.stream().map(idExtractor).filter(Objects::nonNull).collect(Collectors.toSet());Set<ID> newIds = safeNews.stream().map(idExtractor).filter(Objects::nonNull).collect(Collectors.toSet());// 过滤删除ID集合List<ID> removeIdList = oldIds.stream().filter(id -> !newIds.contains(id)).collect(Collectors.toList());// 新增List<T> addList = safeNews.stream().filter(item -> idExtractor.apply(item) == null).collect(Collectors.toList());// 构建旧数据映射Map<ID, T> oldMap = safeOlds.stream().collect(Collectors.toMap(idExtractor, Function.identity()));// 修改项(比较指定字段)List<T> modifyList = safeNews.stream().filter(item -> {ID id = idExtractor.apply(item);if (id == null || !oldMap.containsKey(id)) {return false;}T oldItem = oldMap.get(id);return hasFieldsChanged(oldItem, item, fieldSelectors);}).collect(Collectors.toList());return new DiffWrapper<>(addList, removeIdList, modifyList);}public static void main(String[] args) {// 测试数据准备class TestItem {Long id;String name;TestItem(Long id, String name) {this.id = id;this.name = name;}Long getId() {return id;}String getName() {return name;}@Overridepublic String toString() {return "TestItem[id=" + id + ",name=" + name + "]";}}// 场景1: 正常增删改List<TestItem> oldList1 = Arrays.asList(new TestItem(1L, "old1"), new TestItem(2L, "old2"), new TestItem(3L, "old3"));List<TestItem> newList1 = Arrays.asList(new TestItem(null, "new1"), // 新增new TestItem(2L, "updated2"), // 修改new TestItem(3L, "old3") // 未修改(内容相同));log.info("==== Scenario 1: Normal additions, deletions, and modifications ====");DiffWrapper<TestItem, Long> result1 = DiffWrapper.newInstance(oldList1, newList1, TestItem::getId);log.info("to_add_list: {}", result1.getToAddList()); // 应包含 [new1]log.info("to_remove_id_list: {}: ", result1.getToRemoveIdList()); // 应包含 [1]log.info("to_modify_list: {} ", result1.getToModifyList()); // 应包含 [updated2]// 场景2: 空集合测试log.info("\n==== Scenario 2: Empty collection test ====");DiffWrapper<TestItem, Long> result2 = DiffWrapper.newInstance(null, Collections.emptyList(), TestItem::getId);log.info("empty old result {} ", result2.getToAddList() + "/" + result2.getToRemoveIdList() + "/"+ result2.getToModifyList());// 场景3: 纯新增场景List<TestItem> newList3 = Arrays.asList(new TestItem(null, "new1"), new TestItem(null, "new2"));log.info("\n==== Scenario 3: Pure Addition ====");DiffWrapper<TestItem, Long> result3 = DiffWrapper.newInstance(Collections.emptyList(), newList3, TestItem::getId);log.info("to_add_list {} ", result3.getToAddList().size()); // 应为2// 场景4: 纯删除场景log.info("\n==== Scenario 4: Pure Delete ====");DiffWrapper<TestItem, Long> result4 = DiffWrapper.newInstance(oldList1, Collections.emptyList(), TestItem::getId);log.info("to_remove_id_list: {}", result4.getToRemoveIdList()); // 应包含1,2,3// 场景5: ID为null的特殊情况List<TestItem> oldList5 = Arrays.asList(new TestItem(null, "shouldNotExist"), new TestItem(1L, "validItem"));List<TestItem> newList5 = Arrays.asList(new TestItem(1L, "updated"));log.info("\n==== Scenario 5: Old data contains null ID ====");DiffWrapper<TestItem, Long> result5 = DiffWrapper.newInstance(oldList5, newList5, TestItem::getId);log.info("result: {} ", result5.getToRemoveIdList()); // 只应包含1// 场景6: 内容相同不应出现在修改列表log.info("\n==== Scenario 6: Content Identification Detection ====");DiffWrapper<TestItem, Long> result6 = DiffWrapper.newInstance(oldList1, Arrays.asList(new TestItem(3L, "old3")), TestItem::getId, TestItem::getName);log.info("Modification list should be empty: " + result6.getToModifyList());}/*** 构造函数** @param toAddList* @param toRemoveIdList* @param toModifyList*/private DiffWrapper(List<T> toAddList, List<ID> toRemoveIdList, List<T> toModifyList) {this.toAddList = Collections.unmodifiableList(toAddList);this.toRemoveIdList = Collections.unmodifiableList(toRemoveIdList);this.toModifyList = Collections.unmodifiableList(toModifyList);}/*** 检查指定字段是否发生变化** @param oldItem* @param newItem* @param fieldSelectors* @param <T>* @return*/@SafeVarargsprivate static <T> boolean hasFieldsChanged(T oldItem, T newItem, Function<T, ?>... fieldSelectors) {if (fieldSelectors == null || fieldSelectors.length == 0) {// 默认比较策略return !Objects.equals(oldItem, newItem);}// 比较选中的字段for (Function<T, ?> selector : fieldSelectors) {Object oldVal = selector.apply(oldItem);Object newVal = selector.apply(newItem);if (!Objects.equals(oldVal, newVal)) {// 不相等, 需要更新return true;}}return false;}
调用demo
@Override@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void modifyAccountingNoteItems(Long merchantId, Long noteId, List<AccountingNoteItemDO> newItems) {// 1. 获取数据库现有项List<AccountingNoteItemDO> dbItems = this.listByMerchantIdAndNoteIds(merchantId, Lists.newArrayList(noteId));// 2. 使用工具类计算更新操作DiffWrapper<AccountingNoteItemDO, Long> diffWrapper = DiffWrapper.newInstance(dbItems, newItems, AccountingNoteItemDO::getId);// 3. 执行批量操作if (!diffWrapper.getToAddList().isEmpty()) {this.saveBatch(diffWrapper.getToAddList());}if (!diffWrapper.getToModifyList().isEmpty()) {this.updateBatchById(diffWrapper.getToModifyList());}if (!diffWrapper.getToRemoveIdList().isEmpty()) {this.removeLogic(new LambdaQueryWrapperX<AccountingNoteItemDO>().eq(AccountingNoteItemDO::getMerchantId, merchantId).in(AccountingNoteItemDO::getId, diffWrapper.getToRemoveIdList()));}}