一、neo4j介紹
隨著社交、電商、金融、零售、物聯(lián)網(wǎng)等行業(yè)的快速發(fā)展,現(xiàn)實(shí)社會(huì)織起了了一張龐大而復(fù)雜的關(guān)系網(wǎng),傳統(tǒng)數(shù)據(jù)庫很難處理關(guān)系運(yùn)算。大數(shù)據(jù)行業(yè)需要處理的數(shù)據(jù)之間的關(guān)系隨數(shù)據(jù)量呈幾何級數(shù)增長,急需一種支持海量復(fù)雜數(shù)據(jù)關(guān)系運(yùn)算的數(shù)據(jù)庫,圖數(shù)據(jù)庫應(yīng)運(yùn)而生。 世界上很多著名的公司都在使用圖數(shù)據(jù)庫,比如:社交領(lǐng)域:Facebook, Twitter,Linkedin用它來管理社交關(guān)系,實(shí)現(xiàn)好友推薦
二、圖數(shù)據(jù)庫neo4j安裝
下載鏡像:
docker pull neo4j:3.5.0
運(yùn)行容器:
docker run -d -p 7474:7474 -p 7687:7687 --name neo4j-3.5.0 neo4j:3.5.0
停止容器:
docker stop neo4j-3.5.0
啟動(dòng)容器:
docker start neo4j-3.5.0
瀏覽器?http://localhost:7474/?訪問 neo4j 管理后臺(tái),初始賬號/密碼 neo4j/neo4j,會(huì)要求修改初始化密碼,我們修改為 neo4j/123456
三、簡單CQL入門
就像我們平常使用關(guān)系型數(shù)據(jù)庫中的SQL語句一樣,neo4j中可以使用Cypher查詢語言(CQL)進(jìn)行圖形數(shù)據(jù)庫的查詢,我們簡單來看一下增刪改查的用法。
添加節(jié)點(diǎn)
在CQL中,可以通過CREATE
命令去創(chuàng)建一個(gè)節(jié)點(diǎn),創(chuàng)建不含有屬性節(jié)點(diǎn)的語法如下:
CREATE (<node-name>:<label-name>)
在CREATE
語句中,包含兩個(gè)基礎(chǔ)元素,節(jié)點(diǎn)名稱node-name
和標(biāo)簽名稱lable-name
。標(biāo)簽名稱相當(dāng)于關(guān)系型數(shù)據(jù)庫中的表名,而節(jié)點(diǎn)名稱則代指這一條數(shù)據(jù)。 以下面的CREATE
語句為例,就相當(dāng)于在Person
這張表中創(chuàng)建一條沒有屬性的空數(shù)據(jù)。
CREATE (索爾:Person)
而創(chuàng)建包含屬性的節(jié)點(diǎn)時(shí),可以在標(biāo)簽名稱后面追加一個(gè)描繪屬性的json
字符串:
CREATE (
<node-name>:<label-name>
{
<key1>:<value1>,
…
<keyN>:<valueN>
}
)
用下面的語句創(chuàng)建一個(gè)包含屬性的節(jié)點(diǎn):
CREATE (洛基:Person {name:"洛基",title:"詭計(jì)之神"})
查詢節(jié)點(diǎn)
在創(chuàng)建完節(jié)點(diǎn)后,我們就可以使用MATCH
匹配命令查詢已存在的節(jié)點(diǎn)及屬性的數(shù)據(jù),命令的格式如下:
MATCH (<node-name>:<label-name>)
通常,MATCH
命令在后面配合RETURN
、DELETE
等命令使用,執(zhí)行具體的返回或刪除等操作。 執(zhí)行下面的命令:
MATCH (p:Person) RETURN p
查看可視化的顯示結(jié)果:可以看到上面添加的兩個(gè)節(jié)點(diǎn),分別是不包含屬性的空節(jié)點(diǎn)和包含屬性的節(jié)點(diǎn),并且所有節(jié)點(diǎn)會(huì)有一個(gè)默認(rèn)生成的
id
作為唯一標(biāo)識。
刪除節(jié)點(diǎn)
接下來,我們刪除之前創(chuàng)建的不包含屬性的無用節(jié)點(diǎn),上面提到過,需要使用MATCH
配合DELETE
進(jìn)行刪除。
MATCH (p:Person) WHERE id(p)=100
DELETE p
在這條刪除語句中,額外使用了WHERE
過濾條件,它與SQL中的WHERE
非常相似,命令中通過節(jié)點(diǎn)的id
進(jìn)行了過濾。 刪除完成后,再次執(zhí)行查詢操作,可以看到只保留了洛基
這一個(gè)節(jié)點(diǎn)
添加關(guān)聯(lián)
在neo4j圖數(shù)據(jù)庫中,遵循屬性圖模型來存儲(chǔ)和管理數(shù)據(jù),也就是說我們可以維護(hù)節(jié)點(diǎn)之間的關(guān)系。 在上面,我們創(chuàng)建過一個(gè)節(jié)點(diǎn),所以還需要再創(chuàng)建一個(gè)節(jié)點(diǎn)作為關(guān)系的兩端:
CREATE (p:Person {name:"索爾",title:"雷神"})
創(chuàng)建關(guān)系的基本語法如下:
CREATE (<node-name1>:<label-name1>)
- [<relation-name>:<relation-label-name>]
-> (<node-name2>:<label-name2>)
當(dāng)然,也可以利用已經(jīng)存在的節(jié)點(diǎn)創(chuàng)建關(guān)系,下面我們借助MATCH
先進(jìn)行查詢,再將結(jié)果進(jìn)行關(guān)聯(lián),創(chuàng)建兩個(gè)節(jié)點(diǎn)之間的關(guān)聯(lián)關(guān)系:
MATCH (m:Person),(n:Person)
WHERE m.name='索爾' and n.name='洛基'
CREATE (m)-[r:BROTHER {relation:"無血緣兄弟"}]->(n)
RETURN r
添加完成后,可以通過關(guān)系查詢符合條件的節(jié)點(diǎn)及關(guān)系:
MATCH (m:Person)-[re:BROTHER]->(n:Person)
RETURN m,re,n
可以看到兩者之間已經(jīng)添加了關(guān)聯(lián):需要注意的是,如果節(jié)點(diǎn)被添加了關(guān)聯(lián)關(guān)系后,單純刪除節(jié)點(diǎn)的話會(huì)報(bào)錯(cuò),:
Neo.ClientError.Schema.ConstraintValidationFailed
Cannot delete node<85>, because it still has relationships. To delete this node, you must first delete its relationships.
這時(shí),需要在刪除節(jié)點(diǎn)時(shí)同時(shí)刪除關(guān)聯(lián)關(guān)系:
MATCH (m:Person)-[r:BROTHER]->(n:Person)
DELETE m,r
執(zhí)行上面的語句,就會(huì)在刪除節(jié)點(diǎn)的同時(shí),刪除它所包含的關(guān)聯(lián)關(guān)系了。 那么,簡單的cql語句入門到此為止,它已經(jīng)基本能夠滿足我們的簡單業(yè)務(wù)場景了,下面我們開始在springboot中整合neo4j。
四、springboot整合neo4j
?pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>neo4j</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.2.4</version>
</dependency>
<dependency>
<groupId>edu.stanford.nlp</groupId>
<artifactId>stanford-parser</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
屬性文件
server:
port: 8088
spring:
data:
neo4j:
uri: bolt://127.0.0.1:7687
username: neo4j
password: 123456
文本SPO抽取
在項(xiàng)目中構(gòu)建知識圖譜時(shí),很大一部分場景是基于非結(jié)構(gòu)化的數(shù)據(jù),而不是由我們手動(dòng)輸入確定圖譜中的節(jié)點(diǎn)或關(guān)系。因此,我們需要基于文本進(jìn)行知識抽取的能力,簡單來說就是要在一段文本中抽取出SPO主謂賓三元組,來構(gòu)成圖譜中的點(diǎn)和邊。 這里我們借助Git上一個(gè)現(xiàn)成的工具類,來進(jìn)行文本的語義分析和SPO三元組的抽取工作,
項(xiàng)目地址:https://github.com/hankcs/MainPartExtracto
package com.et.neo4j.hanlp;
import com.et.neo4j.util.GraphUtil;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import edu.stanford.nlp.ling.Word;
import edu.stanford.nlp.parser.lexparser.LexicalizedParser;
import edu.stanford.nlp.trees.*;
import edu.stanford.nlp.trees.international.pennchinese.ChineseTreebankLanguagePack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* 提取主謂賓
*
* @author hankcs
*/
public class MainPartExtractor
{
private static final Logger LOG = LoggerFactory.getLogger(MainPartExtractor.class);
private static LexicalizedParser lp;
private static GrammaticalStructureFactory gsf;
static
{
//模型
String models = "models/chineseFactored.ser";
LOG.info("載入文法模型:" + models);
lp = LexicalizedParser.loadModel(models);
//漢語
TreebankLanguagePack tlp = new ChineseTreebankLanguagePack();
gsf = tlp.grammaticalStructureFactory();
}
/**
* 獲取句子的主謂賓
*
* @param sentence 問題
* @return 問題結(jié)構(gòu)
*/
public static MainPart getMainPart(String sentence)
{
// 去掉不可見字符
sentence = sentence.replace("\\s+", "");
// 分詞,用空格隔開
List<Word> wordList = seg(sentence);
return getMainPart(wordList);
}
/**
* 獲取句子的主謂賓
*
* @param words HashWord列表
* @return 問題結(jié)構(gòu)
*/
public static MainPart getMainPart(List<Word> words)
{
MainPart mainPart = new MainPart();
if (words == null || words.size() == 0) return mainPart;
Tree tree = lp.apply(words);
LOG.info("句法樹:{}", tree.pennString());
// 根據(jù)整個(gè)句子的語法類型來采用不同的策略提取主干
switch (tree.firstChild().label().toString())
{
case "NP":
// 名詞短語,認(rèn)為只有主語,將所有短N(yùn)P拼起來作為主語即可
mainPart = getNPPhraseMainPart(tree);
break;
default:
GrammaticalStructure gs = gsf.newGrammaticalStructure(tree);
Collection<TypedDependency> tdls = gs.typedDependenciesCCprocessed(true);
LOG.info("依存關(guān)系:{}", tdls);
TreeGraphNode rootNode = getRootNode(tdls);
if (rootNode == null)
{
return getNPPhraseMainPart(tree);
}
LOG.info("中心詞語:", rootNode);
mainPart = new MainPart(rootNode);
for (TypedDependency td : tdls)
{
// 依存關(guān)系的出發(fā)節(jié)點(diǎn),依存關(guān)系,以及結(jié)束節(jié)點(diǎn)
TreeGraphNode gov = td.gov();
GrammaticalRelation reln = td.reln();
String shortName = reln.getShortName();
TreeGraphNode dep = td.dep();
if (gov == rootNode)
{
switch (shortName)
{
case "nsubjpass":
case "dobj":
case "attr":
mainPart.object = dep;
break;
case "nsubj":
case "top":
mainPart.subject = dep;
break;
}
}
if (mainPart.object != null && mainPart.subject != null)
{
break;
}
}
// 嘗試合并主語和謂語中的名詞性短語
combineNN(tdls, mainPart.subject);
combineNN(tdls, mainPart.object);
if (!mainPart.isDone()) mainPart.done();
}
return mainPart;
}
private static MainPart getNPPhraseMainPart(Tree tree)
{
MainPart mainPart = new MainPart();
StringBuilder sbResult = new StringBuilder();
List<String> phraseList = getPhraseList("NP", tree);
for (String phrase : phraseList)
{
sbResult.append(phrase);
}
mainPart.result = sbResult.toString();
return mainPart;
}
/**
* 從句子中提取最小粒度的短語
* @param type
* @param sentence
* @return
*/
public static List<String> getPhraseList(String type, String sentence)
{
return getPhraseList(type, lp.apply(seg(sentence)));
}
private static List<String> getPhraseList(String type, Tree tree)
{
List<String> phraseList = new LinkedList<String>();
for (Tree subtree : tree)
{
if(subtree.isPrePreTerminal() && subtree.label().value().equals(type))
{
StringBuilder sbResult = new StringBuilder();
for (Tree leaf : subtree.getLeaves())
{
sbResult.append(leaf.value());
}
phraseList.add(sbResult.toString());
}
}
return phraseList;
}
/**
* 合并名詞性短語為一個(gè)節(jié)點(diǎn)
* @param tdls 依存關(guān)系集合
* @param target 目標(biāo)節(jié)點(diǎn)
*/
private static void combineNN(Collection<TypedDependency> tdls, TreeGraphNode target)
{
if (target == null) return;
for (TypedDependency td : tdls)
{
// 依存關(guān)系的出發(fā)節(jié)點(diǎn),依存關(guān)系,以及結(jié)束節(jié)點(diǎn)
TreeGraphNode gov = td.gov();
GrammaticalRelation reln = td.reln();
String shortName = reln.getShortName();
TreeGraphNode dep = td.dep();
if (gov == target)
{
switch (shortName)
{
case "nn":
target.setValue(dep.toString("value") + target.value());
return;
}
}
}
}
private static TreeGraphNode getRootNode(Collection<TypedDependency> tdls)
{
for (TypedDependency td : tdls)
{
if (td.reln() == GrammaticalRelation.ROOT)
{
return td.dep();
}
}
return null;
}
/**
* 分詞
*
* @param sentence 句子
* @return 分詞結(jié)果
*/
private static List<Word> seg(String sentence)
{
//分詞
LOG.info("正在對短句進(jìn)行分詞:" + sentence);
List<Word> wordList = new LinkedList<>();
List<Term> terms = HanLP.segment(sentence);
StringBuffer sbLogInfo = new StringBuffer();
for (Term term : terms)
{
Word word = new Word(term.word);
wordList.add(word);
sbLogInfo.append(word);
sbLogInfo.append(' ');
}
LOG.info("分詞結(jié)果為:" + sbLogInfo);
return wordList;
}
public static MainPart getMainPart(String sentence, String delimiter)
{
List<Word> wordList = new LinkedList<>();
for (String word : sentence.split(delimiter))
{
wordList.add(new Word(word));
}
return getMainPart(wordList);
}
/**
* 調(diào)用演示
* @param args
*/
public static void main(String[] args)
{
/* String[] testCaseArray = {
"我一直很喜歡你",
"你被我喜歡",
"美麗又善良的你被卑微的我深深的喜歡著……",
"只有自信的程序員才能把握未來",
"主干識別可以提高檢索系統(tǒng)的智能",
"這個(gè)項(xiàng)目的作者是hankcs",
"hankcs是一個(gè)無門無派的浪人",
"搜索hankcs可以找到我的博客",
"靜安區(qū)體育局2013年部門決算情況說明",
"這類算法在有限的一段時(shí)間內(nèi)終止",
};
for (String testCase : testCaseArray)
{
MainPart mp = MainPartExtractor.getMainPart(testCase);
System.out.printf("%s\t%s\n", testCase, mp);
}*/
mpTest();
}
public static void mpTest(){
String[] testCaseArray = {
"我一直很喜歡你",
"你被我喜歡",
"美麗又善良的你被卑微的我深深的喜歡著……",
"小米公司主要生產(chǎn)智能手機(jī)",
"他送給了我一份禮物",
"這類算法在有限的一段時(shí)間內(nèi)終止",
"如果大海能夠帶走我的哀愁",
"天青色等煙雨,而我在等你",
"我昨天看見了一個(gè)非??蓯鄣男『?
};
for (String testCase : testCaseArray) {
MainPart mp = MainPartExtractor.getMainPart(testCase);
System.out.printf("%s %s %s \n",
GraphUtil.getNodeValue(mp.getSubject()),
GraphUtil.getNodeValue(mp.getPredicate()),
GraphUtil.getNodeValue(mp.getObject()));
}
}
}
動(dòng)態(tài)構(gòu)建知識圖譜
在上面的基礎(chǔ)上,我們就可以在項(xiàng)目中動(dòng)態(tài)構(gòu)建知識圖譜了,新建一個(gè)NodeServiceImpl,其中實(shí)現(xiàn)兩個(gè)關(guān)鍵方法parseAndBind和addNode 首先是根據(jù)句子中抽取的主語或賓語在neo4j中創(chuàng)建節(jié)點(diǎn)的方法,這里根據(jù)節(jié)點(diǎn)的name
判斷是否為已存在的節(jié)點(diǎn),如果存在則直接返回,不存在則添加:
package com.et.neo4j.service;
import com.et.neo4j.entity.Node;
import com.et.neo4j.entity.Relation;
import com.et.neo4j.hanlp.MainPart;
import com.et.neo4j.hanlp.MainPartExtractor;
import com.et.neo4j.repository.NodeRepository;
import com.et.neo4j.repository.RelationRepository;
import com.et.neo4j.util.GraphUtil;
import edu.stanford.nlp.trees.TreeGraphNode;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import sun.plugin.dom.core.Attr;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@Service
@AllArgsConstructor
public class NodeServiceImpl implements NodeService {
private final NodeRepository nodeRepository;
private final RelationRepository relationRepository;
@Override
public Node save(Node node) {
Node save = nodeRepository.save(node);
return save;
}
@Override
public void bind(String name1, String name2, String relationName) {
Node start = nodeRepository.findByName(name1);
Node end = nodeRepository.findByName(name2);
Relation relation =new Relation();
relation.setStartNode(start);
relation.setEndNode(end);
relation.setRelation(relationName);
relationRepository.save(relation);
}
private Node addNode(TreeGraphNode treeGraphNode){
String nodeName = GraphUtil.getNodeValue(treeGraphNode);
Node existNode = nodeRepository.findByName(nodeName);
if (Objects.nonNull(existNode))
return existNode;
Node node =new Node();
node.setName(nodeName);
return nodeRepository.save(node);
}
@Override
public List<Relation> parseAndBind(String sentence) {
MainPart mp = MainPartExtractor.getMainPart(sentence);
TreeGraphNode subject = mp.getSubject(); //主語
TreeGraphNode predicate = mp.getPredicate();//謂語
TreeGraphNode object = mp.getObject(); //賓語
if (Objects.isNull(subject) || Objects.isNull(object))
return null;
Node startNode = addNode(subject);
Node endNode = addNode(object);
String relationName = GraphUtil.getNodeValue(predicate);//關(guān)系詞
List<Relation> oldRelation = relationRepository
.findRelation(startNode, endNode,relationName);
if (!oldRelation.isEmpty())
return oldRelation;
Relation botRelation=new Relation();
botRelation.setStartNode(startNode);
botRelation.setEndNode(endNode);
botRelation.setRelation(relationName);
Relation relation = relationRepository.save(botRelation);
return Arrays.asList(relation);
}
}
本文只是拿出關(guān)鍵代碼作講解,具體的代碼參考代碼倉庫地址里面neo4j模塊
代碼倉庫
https://github.com/Harries/springboot-demo
五、測試
啟動(dòng)java應(yīng)用,輸入以下地址
http://127.0.0.1:8088/parse?sentence=海拉又被稱為死亡女神
http://127.0.0.1:8088/parse?sentence= 死亡女神捏碎了雷神之錘
http://127.0.0.1:8088/parse?sentence=雷神之錘屬于索爾
在圖數(shù)據(jù)庫neo4j里面查詢
MATCH (p:Person) RETURN p
六、引用
https://www.cnblogs.com/trunks2008/p/16706962.html文章來源:http://www.zghlxwxcb.cn/news/detail-838817.html
http://www.liuhaihua.cn/archives/710286.html文章來源地址http://www.zghlxwxcb.cn/news/detail-838817.html
到了這里,關(guān)于spring boot集成neo4j實(shí)現(xiàn)簡單的知識圖譜的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!