系列文章目录
之前的系列文章:
一、概述篇:https://blog.csdn.net/qq_40570699/article/details/143024984
二、入门篇:https://blog.csdn.net/qq_40570699/article/details/143905723
三、进阶篇:
- 1.批量导入数据
文章目录
需求场景
代码版本及依赖在该系列入门篇已阐明
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 节点列表
*/
@Transactional
public 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
@Transactional
public 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 和标识符方法。 - 性能优化:
避免重复查询数据库,支持批量查询和保存,减少数据库交互次数。