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

前言


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

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

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

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

关于Neo4j的博客文章如下:

目录

引出


1.Neo4j是用Java实现的开源NoSQL图数据库;

2.SpringBoot使用Neo4j,继承Neo4jRepository进行简单增删改查;

3.使用Neo4jClient进行复杂的查询;

springBoot整合

1、引入依赖

xml 复制代码
<!--        neo4j的包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-neo4j</artifactId>
        </dependency>

2、配置文件

yml 复制代码
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)
java 复制代码
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);
}

复杂查询

最短路径查询

sql 复制代码
//查询两个网点之间最短路径,查询深度最大为10
MATCH path = shortestPath((n:AGENCY) -[*..10]->(m:AGENCY))WHERE n.name = "北京市昌平区定泗路" AND m.name = "上海市浦东新区南汇"RETURN path
java 复制代码
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,就返回结果
    }
}

最小成本查询

java 复制代码
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进行复杂的查询;

相关推荐
笨小孩@GF 知行合一1 小时前
易语言-高级应用
数据库·编程·易语言·中文编程
KaiwuDB2 小时前
KaiwuDB 运维实战04:DRBD + KaiwuDB——物联网场景下的低成本数据库高可用方案
运维·数据库·物联网·时序数据库·kaiwudb·aiot·多模数据库
Zhou1411363 小时前
SpringBoot_03_Web开发
前端·spring boot·python
夜雪一千3 小时前
MySQL外键彻底入门:是什么、怎么用、什么时候不该用
数据库·mysql
qq_334466863 小时前
删除SSMS的历史登录记录
数据库·sql
zl_dfq4 小时前
Redis 之 【常见面试题总结】(从数据类型到高可用、缓存一致性全解析)
数据库·redis
龙亘川4 小时前
铭记英烈守初心,数字强基促服务:亘川智城退役军人服务系统的实践与价值
java·数据库·人工智能
Zhou1411364 小时前
SpringBoot_02_自动配置原理
java·spring boot·后端
FYKJ_20104 小时前
SSM校园失物招领系统41452-计算机课程设计、毕业设计
java·vue.js·spring boot·python·mysql·typescript·spark