前言
图形数据库是专门用于存储图形数据的数据库,它使用图形模型来存储数据,并且支持复杂的图形查询。常见的图形数据库有Neo4j、OrientDB等。
Neo4j是用Java实现的开源NoSQL图数据库,本篇博客介绍如何在SpringBoot中使用Neo4j图数据库,如何进行简单的增删改查,以及如何进行复杂的查询。
本篇博客相关代码的git网址如下:
https://gitee.com/pet365/spring-boot-neo4j
关于Neo4j的博客文章如下:
- 图数据库Neo4j——Neo4j简介、数据结构 & Docker版本的部署安装 & Cypher语句的入门
目录
引出
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:9902logging: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 snippetAfterfindByLaunchDateAfter(Date date)n.launchDate > dateBeforefindByLaunchDateBefore(Date date)n.launchDate < dateContaining (String)findByNameContaining(String namePart)n.name CONTAINS namePartContaining (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 namesBetweenfindByScoreBetween(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 < maxStartingWithfindByNameStartingWith(String nameStart)n.name STARTS WITH nameStartEndingWithfindByNameEndingWith(String nameEnd)n.name ENDS WITH nameEndExistsfindByNameExists()EXISTS(n.name)TruefindByActivatedIsTrue()n.activated = trueFalsefindByActivatedIsFalse()NOT(n.activated = true)IsfindByNameIs(String name)n.name = nameNotNullfindByNameNotNull()NOT(n.name IS NULL)NullfindByNameNull()n.name IS NULLGreaterThanfindByScoreGreaterThan(double score)n.score > scoreGreaterThanEqualfindByScoreGreaterThanEqual(double score)n.score >= scoreLessThanfindByScoreLessThan(double score)n.score < scoreLessThanEqualfindByScoreLessThanEqual(double score)n.score <= scoreLikefindByNameLike(String name)n.name =~ nameNotLikefindByNameNotLike(String name)NOT(n.name =~ name)NearfindByLocationNear(Distance distance, Point point)distance( point(n),point({latitude:lat, longitude:lon}) ) < distanceRegexfindByNameRegex(String regex)n.name =~ regexAndfindByNameAndDescription(String name, String description)n.name = name AND n.description = descriptionOrfindByNameOrDescription(String name, String description)n.name = name OR n.description = description (Cannot be used to OR nested properties)
packagecom.tianju.mapper;importcom.tianju.entity.AgencyEntity;importorg.mapstruct.Mapper;importorg.springframework.data.neo4j.repository.Neo4jRepository;/**
* 网点的mapper,比如菜鸟驿站
*/@MapperpublicinterfaceAgencyMapperextendsNeo4jRepository<AgencyEntity,Long>{/**
* 根据bid 查询
* @param bid 业务id
* @return 网点数据
*/AgencyEntityfindByBid(Long bid);/**
* 根据bid删除
*
* @param bid 业务id
* @return 删除的数据条数
*/LongdeleteByBid(Long bid);}
复杂查询
最短路径查询
//查询两个网点之间最短路径,查询深度最大为10MATCH path = shortestPath((n:AGENCY)-[*..10]->(m:AGENCY))WHERE n.name ="北京市昌平区定泗路"AND m.name ="上海市浦东新区南汇"RETURN path
packagecom.tianju.mapper.impl;importcn.hutool.core.bean.BeanUtil;importcn.hutool.core.collection.CollUtil;importcn.hutool.core.map.MapUtil;importcn.hutool.core.util.StrUtil;importcn.hutool.db.meta.Column;importcom.tianju.dto.OrganDTO;importcom.tianju.dto.TransportLineNodeDTO;importcom.tianju.entity.AgencyEntity;importcom.tianju.enums.OrganTypeEnum;importcom.tianju.mapper.TransportLineRepository;importorg.neo4j.driver.internal.InternalPoint2D;importorg.neo4j.driver.types.Path;importorg.neo4j.driver.types.Relationship;importorg.springframework.data.neo4j.core.schema.Node;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.neo4j.core.Neo4jClient;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importorg.springframework.stereotype.Component;importjava.util.Map;importjava.util.Optional;@ComponentpublicclassTransportLineRepositoryImplimplementsTransportLineRepository{@AutowiredprivateNeo4jClient neo4jClient;/**
* 查询最短路线
* @param start 开始网点
* @param end 结束网点
* @return
*/@OverridepublicTransportLineNodeDTOfindShortestPath(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 =newTransportLineNodeDTO();
path.nodes().forEach(node ->{// 将每个节点信息封装成一个 OrganDto// 获得节点的 键值对 address: 上海市转运中心;bid:8002Map<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);// 把键值对转换成对象 OrganDTOOrganDTO 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,就返回结果}}
最小成本查询
packagecom.tianju.mapper.impl;importcn.hutool.core.bean.BeanUtil;importcn.hutool.core.collection.CollUtil;importcn.hutool.core.map.MapUtil;importcn.hutool.core.util.StrUtil;importcn.hutool.db.meta.Column;importcom.tianju.dto.OrganDTO;importcom.tianju.dto.TransportLineNodeDTO;importcom.tianju.entity.AgencyEntity;importcom.tianju.enums.OrganTypeEnum;importcom.tianju.mapper.TransportLineRepository;importorg.neo4j.driver.internal.InternalPoint2D;importorg.neo4j.driver.types.Path;importorg.neo4j.driver.types.Relationship;importorg.springframework.data.neo4j.core.schema.Node;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.neo4j.core.Neo4jClient;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importorg.springframework.stereotype.Component;importjava.util.Map;importjava.util.Optional;@ComponentpublicclassTransportLineRepositoryImplimplementsTransportLineRepository{@AutowiredprivateNeo4jClient neo4jClient;@OverridepublicTransportLineNodeDTOfindCostLeastPath(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 =newTransportLineNodeDTO();
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);}privatevoidfindShortestPathMy(){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 =newThreadPoolTaskExecutor();}}
总结
1.Neo4j是用Java实现的开源NoSQL图数据库;
2.SpringBoot使用Neo4j,继承Neo4jRepository进行简单增删改查;
3.使用Neo4jClient进行复杂的查询;
版权归原作者 Perley620 所有, 如有侵权,请联系我们删除。