当前位置: 首页 > 科技观察

借助Neo4j与SpringBoot的整合,整理了《雷神》♀

时间:2023-03-14 01:20:26 科技观察

中错综复杂的人物关系大家好,我是Hydra。虽然距离中秋假期还有漫长的两天,但也传来了好消息。今天是《雷神4》将上线Disney+流媒体的日子(也就是说,我们稍后可以在线与大家见面)小伙伴们应该知道,它的神话系统可以用一个词来形容,那就是“混沌”!就像雷神3下面错综复杂的关系网,也只能算是其中的一个半。在上一篇文章中,我们介绍了关于知识图谱的一些基础理论知识。俗话说,不练假把戏,说说就好。今天我们就来看看如何在springboot项目中实现和呈现这个。张雷神复杂的人物关系图。本文将使用以下主要模块构建自然界实体间的联系,实现知识图谱的描述:图数据库neo4j安装简单CQL入口springboot集成neo4j文本SPO抽取知识图谱动态构建Neo4j安装知识图谱底层依赖在关键图数据库上,这里我们选择Neo4j,它是一个高性能的nosql图数据库,可以将结构化数据存储为图而不是表。首先安装,打开官网下载Neo4j安装包,下载免费社区版。地址如下:https://neo4j.com/download/other-releases/。需要注意的是,neo4j4.x以上版本需要依赖jdk11环境,所以如果运行环境是jdk8,那还是老老实实下载3.x版本比较好。下载解压完成后,通过bin目录下的命令启动:neo4j控制台启动后,在浏览器中访问安装服务器的7474端口,打开neo4j控制台页面:通过左侧导航栏,我们可以依次查看存储的数据、一些基本的查询示例、一些帮助说明。顶部带有$符号的输入框可用于输入和执行neo4j特定的CQL查询语句。下面将介绍具体的语法。从简单的CQL开始就像我们通常在关系数据库中使用SQL语句一样,neo4j可以使用Cypher查询语言(CQL)来查询图数据库。下面简单看一下增删改查的用法。添加节点在CQL中,您可以使用CREATE命令创建节点。创建没有属性的节点的语法如下:CREATE(:)在CREATE语句中包含两个基本元素,节点名称node-name和标签名称lable-name。标签名相当于关系数据库中的表名,节点名指的就是这条数据。以下面的CREATE语句为例,相当于在Person表中创建了一条没有属性的空数据。当CREATE(sol:Person)创建一个包含属性的节点时,您可以在标签名称后附加一个描述该属性的json字符串:CREATE(:{:,...:})使用以下语句创建一个包含属性的节点:CREATE(Loki:Person{name:"Loki",title:"TheGodofTrickster"})querynodeaftercreation之后节点,我们可以使用MATCH匹配命令查询已有节点和属性的数据。命令格式如下:MATCH(:)通常,MATCH命令后面跟有RETURN、DELETE等,该命令用于执行返回或删除等特定操作.执行以下命令:MATCH(p:Person)RETURNp查看可视化显示结果:可以看到上面添加的两个节点,分别是没有属性的空节点和有属性的节点,所有的节点都会有一个默认生成的id服务作为唯一标识符。删除节点接下来,我们删除之前创建的不包含属性的无用节点。上面说了,我们需要用MATCH和DELETE来进行删除。MATCH(p:Person)WHEREid(p)=100DELETEp在这个delete语句中,使用了一个额外的WHERE过滤条件,和SQL中的WHERE很相似,通过节点的id来过滤命令。删除完成后,再次执行查询操作,可以看到只保留了节点Loki:在neo4j图数据库中添加关联,按照属性图模型存储和管理数据,也就是说我们可以维护节点之间的关系。上面我们创建了一个节点,所以我们需要再创建一个节点作为关系的两端:CREATE(p:Person{name:"Sol",title:"Thor"})创建关系的基本语法如下:CREATE(:)-[:]->(:)的当然,您也可以使用现有的Existing节点创建关系。接下来,我们先使用MATCH进行查询,然后将结果进行关联,建立两个节点之间的关联关系:MATCH(m:Person),(n:Person)WHEREm.name='Sol'andn.name='Loki'CREATE(m)-[r:BROTHER{relation:"BloodlessBrother"}]->(n)RETURN添加r后,可以通过关系查询满足条件的节点和关系:MATCH(m:Person)-[re:BROTHER]->(n:Person)RETURNm,re,n可以看到两者之间添加了关联:需要注意的是,如果节点添加了关联关系最后,如果你直接删除节点,会报错:Neo.ClientError.Schema.ConstraintValidationFailedCannotdeletenode<85>,因为它还有关系。要删除此节点,您必须先删除其关系。这时候需要同时删除节点关联关系:MATCH(m:Person)-[r:BROTHER]->(n:Person)DELETEm,r执行上面的语句,包含的关联关系其中将在删除节点时被删除。so,简单的cql语句介绍到此结束,基本可以满足我们简单的业务场景。接下来开始在springboot中集成neo4j。SpringBoot集成Neo4j创建springboot工程。这里使用2.3.4版本,引入neo4j的依赖坐标:org.springframework.bootspring-boot-starter-data-neo4j在application.yml中配置neo4j连接信息:spring:data:neo4j:uri:bolt://127.0.0.1:7687用户名:neo4j密码:123456如果你对jpa的应用非常熟练,那么接下来的过程可以说是耳熟能详,因为它们基本上就是一个模式,相同的是构建模型层,存储库层,然后在此基础上操作自定义或者模板方法。节点实体我们可以使用基于注解的实体映射来描述图中的节点,通过在实体类中添加@NodeEntity表示它是图中的一个节点实体,在属性中添加@Property表示它是一个节点中的特定属性。@Data@NodeEntity(label="Person")publicclassNode{@Id@GeneratedValueprivateLongid;@Property(name="name")私有字符串名称;@Property(name="title")privateStringtitle;}这样一个实体类,意味着它创建的实例节点的是Person,每个节点都有name和title两个属性。Repository持久层为上述实体构建持久层接口,继承Neo4jRepository接口,并在接口上添加@Repository注解。@RepositorypublicinterfaceNodeRepositoryextendsNeo4jRepository{@Query("MATCHp=(n:Person)RETURNp")ListselectAll();@Query("MATCH(p:Person{name:{name}})returnp")NodefindByName(Stringname);}接口中加入两个方法供后面测试,selectAll()用于返回所有数据,findByName()用于根据名称查询特定节点。接下来在service层调用repository层的模板方法:@Service@AllArgsConstructorpublicclassNodeServiceImplimplementsNodeService{privatefinalNodeRepositorynodeRepository;@OverridepublicNodesave(Nodenode){Nodesave=nodeRepository.save(node);}返回保存;}}前端调用save()接口,添加一个节点后,再通过查询语句查询控制台,可以通过接口看到新节点已经添加到图中了:addanothermethodintheservice,使用来查询所有节点,直接调用我们在NodeRepository中定义的selectAll()方法:@OverridepublicListgetAll(){Listnodes=nodeRepository.selectAll();nodes.forEach(System.out::println);returnnodes;}查询结果打印在控制台:这里先介绍一下节点的操作,然后开始建立节点之间的关系。关联关系在neo4j中,关联关系其实可以看作是一个特殊的实体,所以可以用实体类来描述。与节点不同的是,需要在类中添加@RelationshipEntity注解,通过@StartNode和@EndNode指定关系的起始节点和结束节点。@Data@RelationshipEntity(type="Relation")publicclassRelation{@Id@GeneratedValueprivateLongid;@StartNode私有节点startNode;@EndNode私有节点endNode;@PropertyprivateStringrelation;}同样,接下来创建一个持久层接口:@RepositorypublicinterfaceRelationRepositoryextendsNeo4jRepository{@Query("MATCHp=(n:Person)-[r:Relation]->(m:Person)"+"WHEREid(n)={startNode}andid(m)={endNode}andr.relation={relation}"+"RETURNp")ListfindRelation(@Param("startNode")NodestartNode,@Param("endNode")NodeendNode,@Param("relation")Stringrelation);}接口中根据起始节点、结束节点及相关内容查询关系的方法为定制的,我们稍后会用到。创建关联在服务层,创建一个方法来根据节点名称建立关联:@Overridepublicvoidbind(Stringname1,Stringname2,StringrelationName){Nodestart=nodeRepository.findByName(name1);nodeend=nodeRepository.findByName(name2);关系relation=newRelation();relation.setStartNode(开始);relation.setEndNode(结束);relation.setRelation(关系名称);relationRepository.save(relation);}通过接口调用该方法,绑定sea拉取Sol和Sol的关系后,查询结果:textSPO提取在项目中构建知识图谱时,很大一部分场景是基于非结构化数据,而不是手动输入图中的节点或关系。因此,我们需要具备基于文本抽取知识的能力。简单来说,我们需要从一段文本中抽取SPO主谓宾三元组,形成图中的节点和边。这里我们使用Git上现成的工具类进行文本语义分析,提取SPO三元组。项目地址:https://github.com/hankcs/MainPartExtractor。虽然这个项目比较简单,一共只有两个类和两个资源文件,但是工具类可以有效的帮助我们完成句子中主谓宾的抽取。使用前需要引入依赖坐标:com.hankcshanlpportable-1.2.4dependency>edu.stanford.nlpstanford-parser3.3.1然后复制com.hankcs.nlp下的两个类本项目中的.lex包到我们的项目中,将models目录下的资源复制到我们的资源中。完成以上步骤后,调用MainPartExtractor工具类中的方法进行简单的文本SPO抽取测试:publicvoidmpTest(){String[]testCaseArray={"我一直很喜欢你","你被我喜欢了","美丽善良的你深爱着卑微的我...","小米公司主要生产智能手机","他送了我一份礼物","限时终止此类算法",“如果大海能带走我的悲伤”、“蔚蓝在等烟雨,我在等你”、“昨天看到一个很可爱的孩子”};for(StringtestCase:testCaseArray){MainPartmp=MainPartExtractor.getMainPart(testCase);System.out.printf("%s%s%s\n",GraphUtil.getNodeValue(mp.getSubject()),GraphUtil.getNodeValue(mp.getPredicate()),GraphUtil.getNodeValue(mp.getObject()));}}在处理结果MainPart中,主谓宾三个属性比较重要,它们的类型是TreeGraphNode,封装了句子的主谓宾成分。我们看一下测试结果:我们可以看到,如果句子中有明确的主谓宾,那么它就会被提取出来。如果一个item为空,则该item为null,剩下的句子结构也可以正常提取。动态构建知识图谱基于以上,我们可以在项目中动态构建知识图谱,新建一个TextAnalysisServiceImpl,实现了两个关键方法。第一种是neo4j中根据从句子中提取的主语或宾语创建节点的方法。这里根据节点的名称判断是否为已有节点。如果存在,则直接返回。如果不存在,则添加:privateNodeaddNode(TreeGraphNodetreeGraphNode){StringnodeName=GraphUtil.getNodeValue(treeGraphNode);节点existNode=nodeRepository.findByName(nodeName);如果(Objects.nonNull(existNode))返回existNode;节点node=newNode();node.setName(nodeName);返回节点存储库。save(node);}然后就是核心方法了,说白了很简单。参数以文本的形式传入句子中,先提取spo,将实体保存为Node,然后检查是否已经存在同名关系。如果不存在,则创建关联关系,如果存在,则不会重复创建。下面是关键代码:@OverridepublicListparseAndBind(Stringsentence){MainPartmp=MainPartExtractor.getMainPart(sentence);TreeGraphNode主题=mp.getSubject();//subjectTreeGraphNodepredicate=mp.getPredicate();//PredicateTreeGraphNodeobject=mp.getObject();//Objectif(Objects.isNull(subject)||Objects.isNull(object))returnnull;节点startNode=addNode(subject);节点endNode=addNode(object);StringrelationName=GraphUtil.getNodeValue(predicate);//关系词ListoldRelation=relationRepository.findRelation(startNode,endNode,relationName);如果(!oldRelation.isEmpty())返回oldRelation;关系botRelation=newRelation();botRelation.setStartNode(startNode);botRelation.setEndNode(endNode);botRelation.setRelation(关系名称);关系relation=relationRepository.save(botRelation);returnArrays.asList(relation);}创建一个用于接收艺术的简单控制器接口Ben:@GetMapping("parse")publicListparse(Stringsentence){returntextAnalysisService.parseAndBind(sentence);}接下来我们从前端传入如下语句进行测试:Hela又名Forthegoddessofdeath,死亡女神压碎了雷神之锤雷神之锤属于雷神调用完成后,我们来看看neo4j中的图形关系。我们可以看到Hela、死亡女神、Thor、Hammer等实体是相互关联的。Together:至此,一个简单的文本处理和地图创建流程已经完全衔接起来,但是这个流程还比较粗糙,需要在以下几个方面进行优化:目前单一类型的节点和Association关系,后面可以在代码中丰富更多类型的节点和关联关系实体。关联关系实体文本中使用的文本spo抽取效果一般。如果应用于企业项目,建议基于更准确的nlp算法做语义分析。当前提取的节点仅包含实体名称,不包含具体属性。未来还需要继续完善补充实体的属性。提高知识融合,主要是加入实体的参考解析和属性的融合。