图数据库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进行复杂的查询;

相关推荐
冒泡的肥皂18 分钟前
MVCC初学demo(一
数据库·后端·mysql
.Shu.1 小时前
Redis Reactor 模型详解【基本架构、事件循环机制、结合源码详细追踪读写请求从客户端连接到命令执行的完整流程】
数据库·redis·架构
薛晓刚4 小时前
当MySQL的int不够用了
数据库
SelectDB技术团队4 小时前
Apache Doris 在菜鸟的大规模湖仓业务场景落地实践
数据库·数据仓库·数据分析·apache doris·菜鸟技术
星空下的曙光5 小时前
mysql 命令语法操作篇 数据库约束有哪些 怎么使用
数据库·mysql
小楓12015 小时前
MySQL數據庫開發教學(一) 基本架構
数据库·后端·mysql
白仑色5 小时前
Spring Boot 全局异常处理
java·spring boot·后端·全局异常处理·统一返回格式
染落林间色5 小时前
达梦数据库-实时主备集群部署详解(附图文)手工搭建一主一备数据守护集群DW
数据库·sql
Monly215 小时前
RabbitMQ:SpringAMQP 入门案例
spring boot·rabbitmq·java-rabbitmq
Monly215 小时前
RabbitMQ:SpringAMQP Fanout Exchange(扇型交换机)
spring boot·rabbitmq·java-rabbitmq