系列文章目录
之前的系列文章:
一、概述篇:https://blog.csdn.net/qq_40570699/article/details/143024984
二、入门篇:https://blog.csdn.net/qq_40570699/article/details/143905723
三、进阶篇:
- 1.批量导入数据
文章目录
- 系列文章目录
- 需求场景
- 一、解决思路
- 二、代码
- 1.将属性更新的逻辑封装为一个通用的工具类,可以处理任意类型的对象。
- 2.在服务端使用上述工具方法封装节点更新逻辑,无需手动处理反射或循环。
- 3.进一步抽象为通用接口
- 4.服务扩展
- 5.代码调用
- 三、总结
需求场景
代码版本及依赖在该系列入门篇已阐明
1.我需要使用Java向Neo4j新增一批数据。若数据已存在则更新非空属性的值,若不存在则新增节点数据。
2.我的节点实体很多,我想要个能够高复用的抽象代码。
一、解决思路
基于我们对ES的更新思路一致,Neo4j提供的saveAll也需要我们先查询后更新。所以我们先将我们插入的数据分类为已存在数据和不存在数据对应的修改和新增。
二、代码
1.将属性更新的逻辑封装为一个通用的工具类,可以处理任意类型的对象。
代码如下(示例):
java">import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Objects;public class ObjectUtils {/*** 更新目标对象的非空属性* * @param source 源对象,包含最新属性值* @param target 目标对象,将被更新*/public static <T> void updateNonNullProperties(T source, T target) {if (source == null || target == null) {throw new IllegalArgumentException("Source and target objects must not be null");}Field[] fields = source.getClass().getDeclaredFields();for (Field field : fields) {try {field.setAccessible(true);Object value = field.get(source);if (value != null) {field.set(target, value);}} catch (IllegalAccessException e) {throw new RuntimeException("Error updating properties", e);}}}/*** 将一批实体更新到数据库(适用于批量更新场景)** @param sources 待更新的源对象集合* @param existingEntities 已存在的实体集合* @param getIdentifierFunction 用于提取唯一标识符的方法引用* @param saveFunction 保存方法引用* @param <T> 实体类型* @param <ID> 唯一标识符类型*/public static <T, ID> void saveOrUpdateEntities(Collection<T> sources,Collection<T> existingEntities,java.util.function.Function<T, ID> getIdentifierFunction,java.util.function.Consumer<Collection<T>> saveFunction) {// 构建已存在实体的 Map<Identifier, Entity>var existingEntityMap = existingEntities.stream().collect(java.util.stream.Collectors.toMap(getIdentifierFunction, e -> e));// 遍历源数据,更新或新增sources.forEach(source -> {ID identifier = getIdentifierFunction.apply(source);T existingEntity = existingEntityMap.get(identifier);if (existingEntity != null) {updateNonNullProperties(source, existingEntity);} else {existingEntities.add(source); // 新增}});// 保存所有数据saveFunction.accept(existingEntities);}
}
2.在服务端使用上述工具方法封装节点更新逻辑,无需手动处理反射或循环。
java">import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;@Service
public class YourNodeService {private final YourNodeRepository yourNodeRepository;public YourNodeService(YourNodeRepository yourNodeRepository) {this.yourNodeRepository = yourNodeRepository;}/*** 插入或更新节点* * @param nodes 节点列表*/@Transactionalpublic void saveOrUpdateNodes(List<YourNode> nodes) {// 提取所有 name 属性List<String> names = nodes.stream().map(YourNode::getName).toList();// 查询数据库中的已存在节点List<YourNode> existingNodes = yourNodeRepository.findByNameIn(names);// 使用通用工具方法处理更新逻辑ObjectUtils.saveOrUpdateEntities(nodes,existingNodes,YourNode::getName,yourNodeRepository::saveAll);}
}
3.进一步抽象为通用接口
java">import java.util.List;public interface GenericService<T, ID> {void saveOrUpdate(List<T> entities, java.util.function.Function<T, ID> identifierFunction);
}
java">import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
import java.util.stream.Collectors;public class GenericServiceImpl<T, ID> implements GenericService<T, ID> {private final CrudRepository<T, ID> repository;public GenericServiceImpl(CrudRepository<T, ID> repository) {this.repository = repository;}@Override@Transactionalpublic void saveOrUpdate(List<T> entities, java.util.function.Function<T, ID> identifierFunction) {// 提取所有标识符List<ID> identifiers = entities.stream().map(identifierFunction).collect(Collectors.toList());// 查询已存在的实体List<T> existingEntities = (List<T>) repository.findAllById(identifiers);// 调用通用工具更新或插入数据ObjectUtils.saveOrUpdateEntities(entities,existingEntities,identifierFunction,repository::saveAll);}
}
4.服务扩展
java">import org.springframework.stereotype.Service;@Service
public class YourNodeService extends GenericServiceImpl<YourNode, String> {public YourNodeService(YourNodeRepository yourNodeRepository) {super(yourNodeRepository);}
}
5.代码调用
java">//name为你实体中标准@Id的唯一属性
yourNodeService.saveOrUpdate(nodes, YourNode::getName);
三、总结
- 复用性强:
通过通用工具类或服务接口实现,支持不同实体类型的批量更新或插入逻辑。 - 代码简洁:
省去每次手写循环处理逻辑,业务层代码更简明。 - 可扩展性:
轻松扩展到其他实体类型,只需注入对应的 Repository 和标识符方法。 - 性能优化:
避免重复查询数据库,支持批量查询和保存,减少数据库交互次数。