图形数据库是专门用于存储图形数据的数据库,它使用图形模型来存储数据,并且支持复杂的图形查询。常见的图形数据库有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进行复杂的查询;
org.springframework.boot spring-boot-starter-data-neo4j
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
提取抽象类
不同的节点类,网点、一级转运中心、二级转运中心
进行自定义查询:
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{ /** * 根据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.RecordFetchSpecrecordFetchSpec = 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 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 map = relationship.asMap(); Double cost = MapUtil.get(map, "cost", Double.class); transportLineNodeDTO.setCost(cost + transportLineNodeDTO.getCost()); }); System.out.println("transportLineNodeDTO: "+transportLineNodeDTO); return transportLineNodeDTO; }); Optional 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); Optionalone = 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 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 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进行复杂的查询;