springboot集成neo4j搭建知识图谱后端项目(一)

springboot集成neo4j搭建知识图谱后端项目(一)

1.概述

getee项目开源地址springboot集成neo4j搭建知识图谱后端项目

本文主要实现springboot集成neo4j,实现动态创建neo4j节点以及关系。数据同时存储在postgresql数据库以及neo4j。

2.安装neo4j

使用docker-compose安装neo4j,可以参考这篇文章docker-compose安装neo4j

3.项目搭建

本项目springboot版本为2.7.18。

3.1.引入pom依赖

xml 复制代码
        <!-- Spring Boot Data Neo4j 依赖,用于集成 Neo4j 数据库 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
            <version>2.7.18</version>
        </dependency>
        <!-- Neo4j Driver -->
        <dependency>
            <groupId>org.neo4j.driver</groupId>
            <artifactId>neo4j-java-driver</artifactId>
            <version>4.4.12</version>
        </dependency>

3.2.添加application.yml配置

yaml 复制代码
spring:
  # Neo4j数据源
  neo4j:
    uri: bolt://192.168.80.196:7687
    authentication:
      username: neo4j
      password: 123456
  # postgresql数据源
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://${ENV_DB_IP:127.0.0.1}:${ENV_DB_PORT:5432}/${ENV_DB_NAME:test}?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true&stringtype=unspecified
    username: postgres
    password: postgres

3.3.添加Neo4jConfig.java配置

java 复制代码
package com.example.graph.config;

import lombok.Data;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * neo4j连接配置
 */
@Configuration
@ConfigurationProperties(prefix = "spring.neo4j")
@Data
public class Neo4jConfig {

    private String uri;
    private Authentication authentication;


    // 创建 Neo4j Driver
    @Bean
    public Driver createDriver() {
        return GraphDatabase.driver(uri, AuthTokens.basic(authentication.getUsername(), authentication.getPassword()));
    }

    // 嵌套类,用于映射 authentication 配置
    @Data
    public static class Authentication {
        private String username;
        private String password;

    }

}

3.4.添加Neo4jService接口

java 复制代码
package com.example.graph.service;

import com.example.graph.entity.Entity;
import com.example.graph.entity.EntityAttribute;
import com.example.graph.vo.NodeRelationVO;
import org.neo4j.driver.Record;
import org.neo4j.driver.types.Node;

import java.util.List;

public interface Neo4jService {


    /**
     * 新增节点
     *
     * @param entity
     */
    <T> void createNode(Entity entity);

    /**
     * 修改节点
     *
     * @param id
     * @param nodeLabel
     * @param attributeList
     */
    <T> void updateNode(String id, String nodeLabel, List<EntityAttribute> attributeList);

    /**
     * 删除节点
     *
     * @param id id
     * @param nodeLabel 节点类型
     */
    void deleteNodeById(String id, String nodeLabel);

    Node findNodeById(String nodeLabel, String id);

    /**
     * 用自定义id属性来创建关系
     *
     * @param fromNode
     * @param toNode
     * @param relationship
     */
    void createRelationship(Node fromNode, Node toNode, String relationship);

    List<NodeRelationVO> selectNodeRelationByPath(String nodeId, String nodeLabel, Integer path);

    void deleteAll();

}

3.5.添加Neo4jServiceImpl实现类

java 复制代码
package com.example.graph.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import com.example.graph.entity.Entity;
import com.example.graph.entity.EntityAttribute;
import com.example.graph.service.Neo4jService;
import com.example.graph.vo.NodeRelationVO;
import com.example.graph.vo.NodeVO;
import com.example.graph.vo.RelationShipVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.types.Node;
import org.neo4j.driver.types.Relationship;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class Neo4jServiceImpl implements Neo4jService {

    private final Driver neo4jDriver;

    @Override
    public <T> void createNode(Entity entity) {
        try (Session session = neo4jDriver.session()) {
            StringBuilder cypherQuery = new StringBuilder("CREATE (n:" + entity.getLabel() + " {");
            cypherQuery.append("id: \"").append(entity.getId()).append("\"").append(", ");
            entity.getAttributeList().stream().forEach(attribute -> {
                cypherQuery.append(attribute.getLabel()).append(": \"").append(attribute.getValue()).append("\"").append(", ");
            });
            cypherQuery.delete(cypherQuery.length() - 2, cypherQuery.length());
            cypherQuery.append("})");

            session.run(cypherQuery.toString());
            log.info("createNode执行cql={}", cypherQuery);
        }
    }

    @Override
    public <T> void updateNode(String id, String nodeLabel, List<EntityAttribute> attributeList) {
        try (Session session = neo4jDriver.session()) {
            StringBuilder cypherQuery = new StringBuilder("MATCH (n:" + nodeLabel + " {id: \"" + id + "\"}) SET ");
            attributeList.stream().forEach(attribute -> {
                cypherQuery.append("n.").append(attribute.getLabel()).append(" = \"").append(attribute.getValue()).append("\", ");
            });
            cypherQuery.delete(cypherQuery.length() - 2, cypherQuery.length());
            cypherQuery.append(" RETURN n");

            session.run(cypherQuery.toString());
            log.info("updateNode执行cql={}", cypherQuery);
        }
    }

    @Override
    public void deleteNodeById(String id, String nodeLabel) {
        try (Session session = neo4jDriver.session()) {
            String cql = StrUtil.format("MATCH (n:{}) WHERE n.id = '{}' DETACH DELETE n", nodeLabel, id);
            session.run(cql);
            log.info("deleteNodeById执行cql={}", cql);
        }
    }


    @Override
    public Node findNodeById(String nodeLabel, String id) {
        try (Session session = neo4jDriver.session()) {
            String cql = StrUtil.format("MATCH (n:{} {id: '{}' }) RETURN n", nodeLabel, id);
            Result result = session.run(cql);
            while (result.hasNext()) {
                Record record = result.next();
                Node node = record.get("n").asNode();
                return node;
            }
            return null;
        }
    }


    @Override
    public void createRelationship(Node fromNode, Node toNode, String relationship) {
        String fromNodeLabel = fromNode.labels().iterator().next();
        Map<String, Object> fromNodeMap = fromNode.asMap();
        String toNodeLabel = toNode.labels().iterator().next();
        Map<String, Object> toNodeMap = toNode.asMap();
        try (Session session = neo4jDriver.session()) {
            String cypherQuery = "MATCH (a:" + fromNodeLabel + " {id: \"" + fromNodeMap.get("id") + "\"}), " +
                    "(b:" + toNodeLabel + " {id: \"" + toNodeMap.get("id") + "\"}) " +
                    "CREATE (a)-[r:" + relationship + "]->(b)";
            session.run(cypherQuery);
            log.info("createRelationship执行cql={}", cypherQuery);
        }
    }

    /**
     * 查询与a节点存在关系、且距离为1的所有节点
     * MATCH (a:student {id: '7'})-[r]-(b) RETURN a, r, b;
     *
     * @param nodeId    节点id
     * @param nodeLabel 节点标签
     */
    @Override
    public List<NodeRelationVO> selectNodeRelationByPath(String nodeId, String nodeLabel, Integer path) {
        try (Session session = neo4jDriver.session()) {
            String cql = StrUtil.format("MATCH (a:{} {id: '{}'})-[r]-(b) RETURN a, r, b", nodeLabel, nodeId);
            if (path > 1) {
                cql = StrUtil.format("MATCH (a:{} {id: '{}'})-[r*1..{}]-(b) RETURN a, r, b", nodeLabel, nodeId, path);
            }
            Result result = session.run(cql);
            List<NodeRelationVO> list = new ArrayList<>();
            while (result.hasNext()) {
                Record record = result.next();
                Node nodeA = record.get("a").asNode();
                Relationship relationship = record.get("r").asRelationship();
                Node nodeB = record.get("b").asNode();

                NodeRelationVO nodeRelationVO = new NodeRelationVO();
                NodeVO fromNodeVO = new NodeVO();
                fromNodeVO.setNodeId(nodeA.id());
                fromNodeVO.setNodeLabel(nodeA.labels().iterator().next());
                fromNodeVO.setNodeProperties(nodeA.asMap());
                RelationShipVO relationShipVO = new RelationShipVO();
                relationShipVO.setRelationType(relationship.type());
                relationShipVO.setRelationProperties(relationship.asMap());
                relationShipVO.setStartNodeId(relationship.startNodeId());
                relationShipVO.setEndNodeId(relationship.endNodeId());
                NodeVO toNodeVO = new NodeVO();
                toNodeVO.setNodeId(nodeB.id());
                toNodeVO.setNodeLabel(nodeB.labels().iterator().next());
                toNodeVO.setNodeProperties(nodeB.asMap());
                nodeRelationVO.setNodeA(fromNodeVO);
                nodeRelationVO.setRelationShipVO(relationShipVO);
                nodeRelationVO.setNodeB(toNodeVO);
                list.add(nodeRelationVO);
            }
            log.info("selectNodeRelation执行cql={}", cql);
            return list;
        }
    }

    @Override
    public void deleteAll() {
        try (Session session = neo4jDriver.session()) {
            String cql = StrUtil.format("MATCH (n) DETACH DELETE n");
            session.run(cql);
            log.info("deleteAll执行cql={}", cql);
        }
    }


}

3.7.调用

在其他类注入,然后调用方法即可

java 复制代码
	@Autowired
    private Neo4jServiceImpl neo4jService;

4.总结

本文主要是采用拼接cql的方式,来实现对neo4j的一些基础操作。

存在问题:可能会存在cql注入风险,后续需要优化。

相关推荐
_码农121381 小时前
spring boot + mybatis + mysql 只有一个实体类的demo
spring boot·mysql·mybatis
c_zyer3 小时前
FreeSWITCH与Java交互实战:从EslEvent解析到Spring Boot生态整合的全指南
spring boot·netty·freeswitch·eslevent
郝学胜-神的一滴3 小时前
Spring Boot Actuator 保姆级教程
java·开发语言·spring boot·后端·程序人生
斜月3 小时前
Springboot 项目加解密的那些事儿
spring boot·后端
草莓爱芒果3 小时前
Spring Boot中使用Bouncy Castle实现SM2国密算法(与前端JS加密交互)
java·spring boot·算法
汤姆yu5 小时前
基于springboot的快递分拣管理系统
java·spring boot·后端
你知道烟火吗8 小时前
谈谈对反射的理解?
java·开发语言·spring boot·后端
it自8 小时前
Redisson在Spring Boot项目中的集成与实战
java·spring boot·redis·后端·缓存
我命由我1234510 小时前
Spring Boot 项目问题:Web server failed to start. Port 5566 was already in use.
java·前端·jvm·spring boot·后端·spring·java-ee