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

前言

图形数据库是专门用于存储图形数据的数据库,它使用图形模型来存储数据,并且支持复杂的图形查询。常见的图形数据库有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

进行自定义查询:

Keyword Sample Cypher snippet
After findByLaunchDateAfter(Date date) n.launchDate > date
Before findByLaunchDateBefore(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)
In findByNameIn(Iterable names) n.name IN names
Between findByScoreBetween(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
StartingWith findByNameStartingWith(String nameStart) n.name STARTS WITH nameStart
EndingWith findByNameEndingWith(String nameEnd) n.name ENDS WITH nameEnd
Exists findByNameExists() EXISTS(n.name)
True findByActivatedIsTrue() n.activated = true
False findByActivatedIsFalse() NOT(n.activated = true)
Is findByNameIs(String name) n.name = name
NotNull findByNameNotNull() NOT(n.name IS NULL)
Null findByNameNull() n.name IS NULL
GreaterThan findByScoreGreaterThan(double score) n.score > score
GreaterThanEqual findByScoreGreaterThanEqual(double score) n.score >= score
LessThan findByScoreLessThan(double score) n.score < score
LessThanEqual findByScoreLessThanEqual(double score) n.score <= score
Like findByNameLike(String name) n.name =~ name
NotLike findByNameNotLike(String name) NOT(n.name =~ name)
Near findByLocationNear(Distance distance, Point point) distance( point(n),point({latitude:lat, longitude:lon}) ) < distance
Regex findByNameRegex(String regex) n.name =~ regex
And findByNameAndDescription(String name, String description) n.name = name AND n.description = description
Or findByNameOrDescription(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进行复杂的查询;

版权声明:本文为博主作者:Perley620原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/Pireley/article/details/134164735

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2024年1月11日
下一篇 2024年1月11日

相关推荐