前言
圖形數(shù)據(jù)庫是專門用于存儲(chǔ)圖形數(shù)據(jù)的數(shù)據(jù)庫,它使用圖形模型來存儲(chǔ)數(shù)據(jù),并且支持復(fù)雜的圖形查詢。常見的圖形數(shù)據(jù)庫有Neo4j、OrientDB等。
Neo4j是用Java實(shí)現(xiàn)的開源NoSQL圖數(shù)據(jù)庫,本篇博客介紹如何在SpringBoot中使用Neo4j圖數(shù)據(jù)庫,如何進(jìn)行簡(jiǎn)單的增刪改查,以及如何進(jìn)行復(fù)雜的查詢。
本篇博客相關(guān)代碼的git網(wǎng)址如下:
https://gitee.com/pet365/spring-boot-neo4j
關(guān)于Neo4j的博客文章如下:
- 圖數(shù)據(jù)庫Neo4j——Neo4j簡(jiǎn)介、數(shù)據(jù)結(jié)構(gòu) & Docker版本的部署安裝 & Cypher語句的入門
引出
1.Neo4j是用Java實(shí)現(xiàn)的開源NoSQL圖數(shù)據(jù)庫;
2.SpringBoot使用Neo4j,繼承Neo4jRepository進(jìn)行簡(jiǎn)單增刪改查;
3.使用Neo4jClient進(jìn)行復(fù)雜的查詢;文章來源地址http://www.zghlxwxcb.cn/news/detail-740129.html
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、實(shí)體類定義
提取抽象類
不同的節(jié)點(diǎn)類,網(wǎng)點(diǎn)、一級(jí)轉(zhuǎn)運(yùn)中心、二級(jí)轉(zhuǎn)運(yùn)中心
4、dao繼承Neo4jRepository
進(jìn)行自定義查詢:
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;
/**
* 網(wǎng)點(diǎn)的mapper,比如菜鳥驛站
*/
@Mapper
public interface AgencyMapper extends Neo4jRepository<AgencyEntity,Long> {
/**
* 根據(jù)bid 查詢
* @param bid 業(yè)務(wù)id
* @return 網(wǎng)點(diǎn)數(shù)據(jù)
*/
AgencyEntity findByBid(Long bid);
/**
* 根據(jù)bid刪除
*
* @param bid 業(yè)務(wù)id
* @return 刪除的數(shù)據(jù)條數(shù)
*/
Long deleteByBid(Long bid);
}
復(fù)雜查詢
最短路徑查詢
//查詢兩個(gè)網(wǎng)點(diǎn)之間最短路徑,查詢深度最大為10
MATCH path = shortestPath((n:AGENCY) -[*..10]->(m:AGENCY))WHERE n.name = "北京市昌平區(qū)定泗路" AND m.name = "上海市浦東新區(qū)南匯"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 開始網(wǎng)點(diǎn)
* @param end 結(jié)束網(wǎng)點(diǎn)
* @return
*/
@Override
public TransportLineNodeDTO findShortestPath(AgencyEntity start, AgencyEntity end) {
// 獲取網(wǎng)點(diǎn)數(shù)據(jù)在Neo4j中的類型 @Node("AGENCY") @Node("OLT")
String type = AgencyEntity.class.getAnnotation(Node.class).value()[0];
// 構(gòu)造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);
// 執(zhí)行自定義查詢
Neo4jClient.RecordFetchSpec<TransportLineNodeDTO> recordFetchSpec = neo4jClient.query(cql)
.bind(start.getBid()).to("startId") // 替換 $startId
.bind(end.getBid()).to("endId") // 替換 $endId
.fetchAs(TransportLineNodeDTO.class) // 設(shè)置響應(yīng)類型,指定為 TransportLineNodeDTO 類型
.mappedBy((typeSystem, record) -> { // 設(shè)置結(jié)果集映射
Path path = record.get(0).asPath();// 得到第一條路線
TransportLineNodeDTO transportLineNodeDTO = new TransportLineNodeDTO();
path.nodes().forEach(node -> { // 將每個(gè)節(jié)點(diǎn)信息封裝成一個(gè) OrganDto
// 獲得節(jié)點(diǎn)的 鍵值對(duì) address: 上海市轉(zhuǎn)運(yùn)中心;bid:8002
Map<String, Object> map = node.asMap();
// {name=北京市昌平區(qū)定泗路,
// location=Point{srid=4326, x=116.37212849638287, y=40.11765281246394},
// address=北七家鎮(zhèn)定泗路蒼龍街交叉口, bid=100280, phone=010-86392987}
System.out.println("map: "+map);
// 把鍵值對(duì)轉(zhuǎn)換成對(duì)象 OrganDTO
OrganDTO organDTO = BeanUtil.toBeanIgnoreError(map, OrganDTO.class);
// organDTO:
// OrganDTO(id=100280, name=北京市昌平區(qū)定泗路, type=null, phone=010-86392987,
// address=北七家鎮(zhèn)定泗路蒼龍街交叉口, latitude=null, longitude=null)
// type,latitude,longitude 沒有映射成功
System.out.println("organDTO: "+organDTO);
// 獲得標(biāo)簽的名稱 OLT,TLT,
String first = CollUtil.getFirst(node.labels());
// 根據(jù)OLT獲得枚舉類型 OLT(1, "一級(jí)轉(zhuǎn)運(yùn)中心"),
OrganTypeEnum organTypeEnum = OrganTypeEnum.valueOf(first);
// 再獲得枚舉類型的 code :1、2、3
organDTO.setType(organTypeEnum.getCode()); // 設(shè)置類型的映射
// 經(jīng)緯度 "location": point({srid:4326, x:121.59815370294322, y:31.132409729356993})
InternalPoint2D location = MapUtil.get(map, "location", InternalPoint2D.class); // 經(jīng)緯度 BeanUtil.getProperty(map.get("location"),"x");
organDTO.setLatitude(location.x()); // 設(shè)置經(jīng)緯度映射
organDTO.setLongitude(location.y()); // 經(jīng)緯度映射
// OrganDTO(id=100280, name=北京市昌平區(qū)定泗路, type=3,
// phone=010-86392987, address=北七家鎮(zhèn)定泗路蒼龍街交叉口,
// latitude=116.37212849638287, longitude=40.11765281246394)
System.out.println("organDTO: "+organDTO);
transportLineNodeDTO.getNodeList().add(organDTO);
});
System.out.println("transportLineNodeDTO: "+transportLineNodeDTO);
path.relationships().forEach(relationship -> {
// 路徑下面的關(guān)系
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,就返回結(jié)果
}
}
最小成本查詢
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);
// 獲得標(biāo)簽的名稱 OLT,TLT,
String first = CollUtil.getFirst(node.labels());
// 根據(jù)OLT獲得枚舉類型 OLT(1, "一級(jí)轉(zhuǎn)運(yùn)中心"),
OrganTypeEnum organTypeEnum = OrganTypeEnum.valueOf(first);
// 再獲得枚舉類型的 code :1、2、3
organDTO.setType(organTypeEnum.getCode()); // 設(shè)置類型的映射
// 經(jīng)緯度 "location": point({srid:4326, x:121.59815370294322, y:31.132409729356993})
InternalPoint2D location = MapUtil.get(map, "location", InternalPoint2D.class); // 經(jīng)緯度 BeanUtil.getProperty(map.get("location"),"x");
organDTO.setLatitude(location.x()); // 設(shè)置經(jīng)緯度映射
organDTO.setLongitude(location.y()); // 經(jīng)緯度映射
transportLineNodeDTO.getNodeList().add(organDTO);
});
path.relationships().forEach(relationship -> {
// 路徑下面的關(guān)系
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";
// 執(zhí)行自定義查詢
Neo4jClient.UnboundRunnableSpec query = neo4jClient.query(cql);
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
}
}
文章來源:http://www.zghlxwxcb.cn/news/detail-740129.html
總結(jié)
1.Neo4j是用Java實(shí)現(xiàn)的開源NoSQL圖數(shù)據(jù)庫;
2.SpringBoot使用Neo4j,繼承Neo4jRepository進(jìn)行簡(jiǎn)單增刪改查;
3.使用Neo4jClient進(jìn)行復(fù)雜的查詢;
到了這里,關(guān)于圖數(shù)據(jù)庫Neo4j——SpringBoot使用Neo4j & 簡(jiǎn)單增刪改查 & 復(fù)雜查詢初步的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!