【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 和标识符方法。
  • 性能优化:
    避免重复查询数据库,支持批量查询和保存,减少数据库交互次数。
相关推荐
轩情吖19 分钟前
C++类型转换
java·开发语言·c++·多态·c++类型转换·rtti
Langkaixin29 分钟前
idea编译与maven编译的问题
java·maven·intellij-idea
兩尛35 分钟前
搜索二维矩阵 II(java)
java·算法·矩阵
岁岁岁平安1 小时前
JavaWeb实战(1)(重点:分页查询、jstl标签与jsp、EL表达式、Bootstrap组件搭建页面、jdbc)
java·servlet·javaweb·jsp·el·分页查询·jstl
java_upp1 小时前
简简单单实现java系统调用WebServie接口
java·webservice
Allen Bright1 小时前
【maven-4】IDEA 配置本地 Maven 及如何使用 Maven 创建 Java 工程
java·maven·intellij-idea
考虑考虑1 小时前
JDK8加载拓展包
java·后端·java ee
南风不竞~~2 小时前
Maven 配置
java·maven
南风不竞~~2 小时前
neo4j5.25,jdk21,eclipse下载安装全配置
java·eclipse·neo4j
大臣不想在月亮上上热搜2 小时前
黑马2024AI+JavaWeb开发入门Day03-Maven-单元测试飞书作业
java·web