【java-Neo4j 5进阶篇】- 1.批量新增数据

系列文章目录

之前的系列文章:

一、概述篇: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 和标识符方法。
  • 性能优化:
    避免重复查询数据库,支持批量查询和保存,减少数据库交互次数。
相关推荐
shepherd11120 小时前
吞吐量提升 10 倍:高并发大批量数据处理任务的架构演进与性能调优
java·后端·架构
plainGeekDev1 天前
单例模式 → object 声明
android·java·kotlin
用户298698530141 天前
Java 实现 Word 文档文本与图片提取的方法
java·后端
SimonKing1 天前
铁子,IntelliJ IDEA 2026.1.3来了,升不升?
java·后端·程序员
咖啡八杯1 天前
GoF设计模式——策略模式
java·后端·spring·设计模式
用户128526116022 天前
我把祖传Java项目重构后,接口响应从3s砍到了200ms,只改了这几行代码
java
Linsk2 天前
组件 = 模板 + 业务逻辑
java·前端·vue.js
星沉远浦2 天前
用Gemini高效解决Java代码报错难以定位的问题
java
用户298698530142 天前
Word 文档字符级格式化:Java 实现方案详解
java·后端