图数据库Neo4j——SpringBoot使用Neo4j 简单增删改查 复杂查询初步

news2025/2/25 9:47:16

在这里插入图片描述

前言


图形数据库是专门用于存储图形数据的数据库,它使用图形模型来存储数据,并且支持复杂的图形查询。常见的图形数据库有Neo4j、OrientDB等。

Neo4j是用Java实现的开源NoSQL图数据库,本篇博客介绍如何在SpringBoot中使用Neo4j图数据库,如何进行简单的增删改查,以及如何进行复杂的查询。

本篇博客相关代码的git网址如下:

https://gitee.com/pet365/spring-boot-neo4j

关于Neo4j的博客文章如下:

  • 图数据库Neo4j——Neo4j简介、数据结构 & Docker版本的部署安装 & Cypher语句的入门

目录

  • 前言
  • 引出
  • springBoot整合
    • 1、引入依赖
    • 2、配置文件
    • 3、实体类定义
    • 4、dao继承Neo4jRepository
    • 复杂查询
      • 最短路径查询
      • 最小成本查询
  • 总结

引出


1.Neo4j是用Java实现的开源NoSQL图数据库;
2.SpringBoot使用Neo4j,继承Neo4jRepository进行简单增删改查;
3.使用Neo4jClient进行复杂的查询;

springBoot整合

1、引入依赖

<!--        neo4j的包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>

2、配置文件

server:
  port: 9902
logging:
  level:
    org.springframework.data.neo4j: debug
spring:
  application:
    name: spring-neo4j
  data:
    neo4j:
      database: neo4j
  neo4j:
    authentication:
      username: neo4j
      password: neo4j123
    uri: neo4j://192.168.150.101:7687

3、实体类定义

在这里插入图片描述

提取抽象类

在这里插入图片描述

不同的节点类,网点、一级转运中心、二级转运中心

在这里插入图片描述

4、dao继承Neo4jRepository

进行自定义查询:

KeywordSampleCypher snippet
AfterfindByLaunchDateAfter(Date date)n.launchDate > date
BeforefindByLaunchDateBefore(Date date)n.launchDate < date
Containing (String)findByNameContaining(String namePart)n.name CONTAINS namePart
Containing (Collection)findByEmailAddressesContains(Collection addresses) findByEmailAddressesContains(String address)ANY(collectionFields IN [addresses] WHERE collectionFields in n.emailAddresses) ANY(collectionFields IN address WHERE collectionFields in n.emailAddresses)
InfindByNameIn(Iterable names)n.name IN names
BetweenfindByScoreBetween(double min, double max) findByScoreBetween(Range range)n.score >= min AND n.score <= max Depending on the Range definition n.score >= min AND n.score <= max or n.score > min AND n.score < max
StartingWithfindByNameStartingWith(String nameStart)n.name STARTS WITH nameStart
EndingWithfindByNameEndingWith(String nameEnd)n.name ENDS WITH nameEnd
ExistsfindByNameExists()EXISTS(n.name)
TruefindByActivatedIsTrue()n.activated = true
FalsefindByActivatedIsFalse()NOT(n.activated = true)
IsfindByNameIs(String name)n.name = name
NotNullfindByNameNotNull()NOT(n.name IS NULL)
NullfindByNameNull()n.name IS NULL
GreaterThanfindByScoreGreaterThan(double score)n.score > score
GreaterThanEqualfindByScoreGreaterThanEqual(double score)n.score >= score
LessThanfindByScoreLessThan(double score)n.score < score
LessThanEqualfindByScoreLessThanEqual(double score)n.score <= score
LikefindByNameLike(String name)n.name =~ name
NotLikefindByNameNotLike(String name)NOT(n.name =~ name)
NearfindByLocationNear(Distance distance, Point point)distance( point(n),point({latitude:lat, longitude:lon}) ) < distance
RegexfindByNameRegex(String regex)n.name =~ regex
AndfindByNameAndDescription(String name, String description)n.name = name AND n.description = description
OrfindByNameOrDescription(String name, String description)n.name = name OR n.description = description (Cannot be used to OR nested properties)

在这里插入图片描述

package com.tianju.mapper;

import com.tianju.entity.AgencyEntity;
import org.mapstruct.Mapper;
import org.springframework.data.neo4j.repository.Neo4jRepository;

/**
 * 网点的mapper,比如菜鸟驿站
 */
@Mapper
public interface AgencyMapper extends Neo4jRepository<AgencyEntity,Long> {
    /**
     * 根据bid 查询
     * @param bid 业务id
     * @return 网点数据
     */
    AgencyEntity findByBid(Long bid);


    /**
     * 根据bid删除
     *
     * @param bid 业务id
     * @return 删除的数据条数
     */
    Long deleteByBid(Long bid);
}

在这里插入图片描述

复杂查询

在这里插入图片描述

最短路径查询

//查询两个网点之间最短路径,查询深度最大为10
MATCH path = shortestPath((n:AGENCY) -[*..10]->(m:AGENCY))WHERE n.name = "北京市昌平区定泗路" AND m.name = "上海市浦东新区南汇"RETURN path

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

package com.tianju.mapper.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.meta.Column;
import com.tianju.dto.OrganDTO;
import com.tianju.dto.TransportLineNodeDTO;
import com.tianju.entity.AgencyEntity;
import com.tianju.enums.OrganTypeEnum;
import com.tianju.mapper.TransportLineRepository;
import org.neo4j.driver.internal.InternalPoint2D;
import org.neo4j.driver.types.Path;
import org.neo4j.driver.types.Relationship;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Optional;

@Component
public class TransportLineRepositoryImpl implements TransportLineRepository {

    @Autowired
    private Neo4jClient neo4jClient;

    /**
     * 查询最短路线
     * @param start 开始网点
     * @param end   结束网点
     * @return
     */
    @Override
    public TransportLineNodeDTO findShortestPath(AgencyEntity start, AgencyEntity end) {
        // 获取网点数据在Neo4j中的类型 @Node("AGENCY") @Node("OLT")
        String type = AgencyEntity.class.getAnnotation(Node.class).value()[0];
        // 构造Sql语句 $startId
//        String cql = "MATCH path = shortestPath((n:AGENCY) -[*..10]->(m:AGENCY))\n" +
//                "WHERE n.bid = $startId AND m.bid = $endId\n" +
//                "RETURN path";
        String cql = StrUtil.format("MATCH path = shortestPath((n:{}) -[*..10]->(m:{})) " +
                "WHERE n.bid = $startId AND m.bid = $endId " +
                "RETURN path",type,type);
        // 执行自定义查询
        Neo4jClient.RecordFetchSpec<TransportLineNodeDTO> recordFetchSpec = neo4jClient.query(cql)
                .bind(start.getBid()).to("startId") // 替换 $startId
                .bind(end.getBid()).to("endId")  // 替换 $endId
                .fetchAs(TransportLineNodeDTO.class)   // 设置响应类型,指定为 TransportLineNodeDTO 类型
                .mappedBy((typeSystem, record) -> {    // 设置结果集映射
                    Path path = record.get(0).asPath();// 得到第一条路线
                    TransportLineNodeDTO transportLineNodeDTO = new TransportLineNodeDTO();

                    path.nodes().forEach(node -> { // 将每个节点信息封装成一个 OrganDto
                        // 获得节点的 键值对 address: 上海市转运中心;bid:8002
                        Map<String, Object> map = node.asMap();
                        // {name=北京市昌平区定泗路,
                        // location=Point{srid=4326, x=116.37212849638287, y=40.11765281246394},
                        // address=北七家镇定泗路苍龙街交叉口, bid=100280, phone=010-86392987}
                        System.out.println("map: "+map);

                        // 把键值对转换成对象 OrganDTO
                        OrganDTO organDTO = BeanUtil.toBeanIgnoreError(map, OrganDTO.class);
                        // organDTO:
                        // OrganDTO(id=100280, name=北京市昌平区定泗路, type=null, phone=010-86392987,
                        // address=北七家镇定泗路苍龙街交叉口, latitude=null, longitude=null)
                        // type,latitude,longitude 没有映射成功
                        System.out.println("organDTO: "+organDTO);

                        // 获得标签的名称 OLT,TLT,
                        String first = CollUtil.getFirst(node.labels());
                        // 根据OLT获得枚举类型 OLT(1, "一级转运中心"),
                        OrganTypeEnum organTypeEnum = OrganTypeEnum.valueOf(first);
                        // 再获得枚举类型的 code :1、2、3
                        organDTO.setType(organTypeEnum.getCode()); // 设置类型的映射

                        // 经纬度 "location": point({srid:4326, x:121.59815370294322, y:31.132409729356993})
                        InternalPoint2D location = MapUtil.get(map, "location", InternalPoint2D.class); // 经纬度 BeanUtil.getProperty(map.get("location"),"x");
                        organDTO.setLatitude(location.x());  // 设置经纬度映射
                        organDTO.setLongitude(location.y()); // 经纬度映射
                        // OrganDTO(id=100280, name=北京市昌平区定泗路, type=3,
                        // phone=010-86392987, address=北七家镇定泗路苍龙街交叉口,
                        // latitude=116.37212849638287, longitude=40.11765281246394)
                        System.out.println("organDTO: "+organDTO);

                        transportLineNodeDTO.getNodeList().add(organDTO);
                    });

                    System.out.println("transportLineNodeDTO: "+transportLineNodeDTO);

                    path.relationships().forEach(relationship -> {
                        // 路径下面的关系
                        Map<String, Object> map = relationship.asMap();
                        Double cost = MapUtil.get(map, "cost", Double.class);
                        transportLineNodeDTO.setCost(cost + transportLineNodeDTO.getCost());
                    });

                    System.out.println("transportLineNodeDTO: "+transportLineNodeDTO);
                    return transportLineNodeDTO;
                });
        Optional<TransportLineNodeDTO> one = recordFetchSpec.one(); // Optional,1.8提供的,可以处理null的情况
        return one.orElse(null); // 如果为null,就返回null,如果不是null,就返回结果
    }
}

最小成本查询

在这里插入图片描述

package com.tianju.mapper.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.meta.Column;
import com.tianju.dto.OrganDTO;
import com.tianju.dto.TransportLineNodeDTO;
import com.tianju.entity.AgencyEntity;
import com.tianju.enums.OrganTypeEnum;
import com.tianju.mapper.TransportLineRepository;
import org.neo4j.driver.internal.InternalPoint2D;
import org.neo4j.driver.types.Path;
import org.neo4j.driver.types.Relationship;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Optional;

@Component
public class TransportLineRepositoryImpl implements TransportLineRepository {

    @Autowired
    private Neo4jClient neo4jClient;


    @Override
    public TransportLineNodeDTO findCostLeastPath(AgencyEntity start, AgencyEntity end) {
        String type = AgencyEntity.class.getAnnotation(Node.class).value()[0];

        String cqlB = "MATCH path = (n:{}) -[*..10]->(m:{}) " +
                "WHERE n.bid = $startId AND m.bid = $endId " +
                "UNWIND relationships(path) AS r " +
                "WITH sum(r.cost) AS cost, path " +
                "RETURN path ORDER BY cost ASC, LENGTH(path) ASC LIMIT 1";
        String cql = StrUtil.format(cqlB, type,type);

        Optional<TransportLineNodeDTO> one = neo4jClient.query(cql)
                .bind(start.getBid()).to("startId")
                .bind(end.getBid()).to("endId")
                .fetchAs(TransportLineNodeDTO.class)
                .mappedBy(((typeSystem, record) -> {
                    Path path = record.get(0).asPath();
                    TransportLineNodeDTO transportLineNodeDTO = new TransportLineNodeDTO();

                    path.nodes().forEach(node -> {
                        Map<String, Object> map = node.asMap();
                        OrganDTO organDTO = BeanUtil.toBeanIgnoreError(map, OrganDTO.class);
                        // 获得标签的名称 OLT,TLT,
                        String first = CollUtil.getFirst(node.labels());
                        // 根据OLT获得枚举类型 OLT(1, "一级转运中心"),
                        OrganTypeEnum organTypeEnum = OrganTypeEnum.valueOf(first);
                        // 再获得枚举类型的 code :1、2、3
                        organDTO.setType(organTypeEnum.getCode()); // 设置类型的映射

                        // 经纬度 "location": point({srid:4326, x:121.59815370294322, y:31.132409729356993})
                        InternalPoint2D location = MapUtil.get(map, "location", InternalPoint2D.class); // 经纬度 BeanUtil.getProperty(map.get("location"),"x");
                        organDTO.setLatitude(location.x());  // 设置经纬度映射
                        organDTO.setLongitude(location.y()); // 经纬度映射
                        transportLineNodeDTO.getNodeList().add(organDTO);
                    });

                    path.relationships().forEach(relationship -> {
                        // 路径下面的关系
                        Map<String, Object> map = relationship.asMap();
                        Double cost = MapUtil.get(map, "cost", Double.class);
                        transportLineNodeDTO.setCost(cost + transportLineNodeDTO.getCost());
                    });
                    return transportLineNodeDTO;
                })).one();
        return one.orElse(null);
    }

    private void findShortestPathMy(){
        String cql = "MATCH path = shortestPath((n:AGENCY) -[*..10]->(m:AGENCY)) " +
                "WHERE n.bid = 210127 AND m.bid = 100260 " +
                "RETURN path";
        // 执行自定义查询
        Neo4jClient.UnboundRunnableSpec query = neo4jClient.query(cql);

        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();


    }
}

在这里插入图片描述


总结

1.Neo4j是用Java实现的开源NoSQL图数据库;
2.SpringBoot使用Neo4j,继承Neo4jRepository进行简单增删改查;
3.使用Neo4jClient进行复杂的查询;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1158892.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023最新全国拉新app推广接单平台合集 地推网推项目平台渠道

平台 ”聚量推客“ 服务商直营的拉新平台 数据和结算都有保障 地推平台承上启下&#xff0c;对上承接甲方项目&#xff0c;对下对接渠道&#xff0c;方便甲方放单又方便渠道统一接单 以下是全国国内十大地推拉新app推广接单平台分享&#xff0c;2023最新全国拉新app推广接单平…

cec2017(MATLAB):星雀优化算法(Nutcracker optimizer algorithm,NOA)

一、星雀优化算法NOA 星雀优化算法(Nutcracker optimizer algorithm,NOA)由Mohamed Abdel-Basset等人于2023年提出&#xff0c;该算法模拟星雀的两种行为&#xff0c;即&#xff1a;在夏秋季节收集并储存食物&#xff0c;在春冬季节搜索食物的存储位置。星雀优化算法(Nutcrack…

C++ Qt 学习(一):Qt 入门

Qt6 安装教程 0. 基础知识 0.1 qmake 和 cmake 对比 qmake&#xff1a;qt 独有的代码构建工具cmake&#xff1a;C 通用的代码构建工具&#xff0c;绝大部分 C 开源项目都使用 cmake 管理代码qt 项目&#xff0c;没有特殊要求&#xff0c;使用 qmake 即可 0.2 Qt 3 个窗口类的…

代码随想录Day34 LeetCode T343整数拆分 T96 不同的二叉搜索树

目录 前言 LeetCode T343 整数拆分 题目思路: 第一步:确定递归数组含义 第三步:初始化dp数组(其实也是为接下来的递推做准备) 第四步:确定遍历顺序(很多情况下是对遍历顺序有要求的) 第五步:打印dp数组(如果遇到错误可以打印一下dp数组看看和我们推理的dp数组有啥不同,错…

0基础学习PyFlink——使用DataStream进行字数统计

大纲 sourceMapSplittingMapping ReduceKeyingReducing 完整代码结构参考资料 在《0基础学习PyFlink——模拟Hadoop流程》一文中&#xff0c;我们看到Hadoop在处理大数据时的MapReduce过程。 本节介绍的DataStream API&#xff0c;则使用了类似的结构。 source 为了方便&…

OSPF高级特性

OSPF高级特性(1) 一、OSPF不规则区域类型 产生原因&#xff1a;区域划分不合理&#xff0c;导致的问题 1、非骨干区域无法和骨干区域保持连通 2、骨干区域被分割 造成后果&#xff1a;非骨干区域没和骨干区域相连&#xff0c;导致ABR将不会帮忙转发区域间的路由信息。非骨干区…

MS3142电机驱动器可兼容LV8548M

MS3142/MS3142S 是一个双全桥电机驱动。可兼容LV8548M&#xff08;功能基本一致&#xff0c;管脚不兼容&#xff09;。电源电压供电范围 4V 到 18V&#xff0c;平均电流 1.1A&#xff0c;电流峰值 1.54A。如果需要更高的电流能力&#xff0c;可以将双全桥并联使用。 四个输入脚…

数据结构预算法--顺序表

1.顺序表 1.1概念及结构 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。在数组上完成数据的增删查改。 顺序表一般可以分为&#xff1a; 1. 静态顺序表&#xff1a;使用定长数组存储元素。 2. 动态顺序表&#xff1a;使…

Shiro 身份验证绕过 (CVE-2020-13933)

一、漏洞描述 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。 Apache Shiro身份验证绕过漏洞CVE-2020-11989的修复补丁存在缺陷&#xff0c;在1.5.3及其之前的版本&#xff0c;由于shiro在处理url时与spring仍然存在差异&#xff0c;依然存…

Day42 力扣动态规划 :123.买卖股票的最佳时机III |188.买卖股票的最佳时机IV

Day42 力扣动态规划 :123.买卖股票的最佳时机III &#xff5c;188.买卖股票的最佳时机IV 123.买卖股票的最佳时机III第一印象看完题解的思路dp数组&#xff1a;递推公式&#xff1a;初始化遍历顺序 实现中的困难感悟代码 188.买卖股票的最佳时机IV第一印象初始化递推公式看完题…

黄金矿工小游戏

欢迎来到程序小院 黄金矿工 玩法&#xff1a;点击开始游戏&#xff0c;黄金和钩子&#xff0c;钩子会左右摆动&#xff0c;对准黄金位置点击鼠标左键钓起黄金加对应时间&#xff0c;钓起黑色四块减去响应时间&#xff0c;快去挖矿吧^^。开始游戏https://www.ormcc.com/play/ga…

【错误解决方案】Error: module ‘cv2‘ has no attribute ‘SURF‘

1. 错误提示 python-opencv高版本中&#xff0c;AttributeError: module cv2 has no attribute SURF问题&#xff1b; 错误提示&#xff1a;Error: module ‘cv2‘ has no attribute ‘SURF‘ 2. 解决方案 解决&#xff1a;将sift cv2.SIFT()替换为&#xff1a;sift cv2.x…

windows内存取证-中等难度-下篇

上文我们对第一台Target机器进行内存取证&#xff0c;今天我们继续往下学习&#xff0c;内存镜像请从上篇获取&#xff0c;这里不再进行赘述​ Gideon 攻击者访问了“Gideon”&#xff0c;他们向AllSafeCyberSec域控制器窃取文件,他们使用的密码是什么&#xff1f; 攻击者执…

day57--动态规划15

392.判断子序列 115.不同的子序列 第一题&#xff1a;判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&…

大模型冷思考:企业“可控”价值创造空间还有多少?

文 | 智能相对论 作者 | 叶远风 毫无疑问&#xff0c;大模型热潮正一浪高过一浪。 在发展进程上&#xff0c;从最开始的技术比拼到现在已开始全面强调商业价值变现&#xff0c;百度、科大讯飞等厂商都喊出类似“不能落地的大模型没有意义”等口号。 在模型类型上&#xff0…

2023年【高压电工】考试及高压电工找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 高压电工考试参考答案及高压电工考试试题解析是安全生产模拟考试一点通题库老师及高压电工操作证已考过的学员汇总&#xff0c;相对有效帮助高压电工找解析学员顺利通过考试。 1、【单选题】 额定容量是指:在变压器铭…

Spring源码分析篇:@Autowired 是怎样完成注入的?究竟是byType还是byName亦两者皆有

1. 五种不同场景下 Autowired 的使用 第一种情况 上下文中只有一个同类型的bean 配置类 package org.example.bean; ​ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; ​ Configuration public class…

docker-compose 简单部署MySQL Database

docker-compose 简单部署MySQL Database 本博文部署MySQL 并与上篇部署的 Flask进行关联 主博客目录&#xff1a;《从零开始学习搭建量化平台笔记》 文章目录 docker-compose 简单部署MySQL Database部署 MySQLMySQL 开放端口与权限 主项目计划需要搭建一个MySQL 数据库为其他部…

“零基础”PHP代码审计入门

目录 一、代码审计目的 二、代码审计基础 三、 代码审计思路 四、PHP核心配置 五、 代码审计环境 六、手动调试代码 七、PHP的弱类型 八、学习漏洞函数 九、审计入门总结 推荐一些demo&#xff1a; 一、代码审计目的 代码审计指的是对源代码进行检查&#xff0c;寻找…

智慧校园地下管线三维可视化管控平台减少人力和物力资源的浪费

随着科技的不断发展&#xff0c;三维可视化管理平台在各个领域得到了广泛的应用。三维可视化管理平台通过将数据以三维形式呈现&#xff0c;使得用户能够更直观地理解和分析数据&#xff0c;从而提高工作效率和决策质量。 VR数字孪生园区系统是通过将实际园区的各种数据和信息进…