本项目demo地址【请阅读readme文件】:
https://gitee.com/LiuDaiHua/project-neo4j
最近项目上要搭建一个关系图谱的东西,领导给了neo4j和d3两个概念让我去做,最终目的是使用d3.js去完成关系图谱【力导向图】的创建。我们先看几张demo的截图吧!
neo4j:
图1
后台查询返回给前台的数据:
图2
目前,数据已经返回给前台,基本封装好了,后期我在处理,将最终做的效果展现给大家。
目前切先看一下前台d3.js制作力导向图的模拟案例:
图3
在看一下图3模拟案例图中所需数据data.json格式:
{ "nodes":[ {"id":0,"name":"安徽阜阳"}, {"id":1,"name":"安徽淮南"}, {"id":2,"name":"江苏江阴"}, {"id":3,"name":"安徽安庆"}, {"id":4,"name":"毛毛"}, {"id":5,"name":"吉吉"}, {"id":6,"name":"可可"}, {"id":7,"name":"咪咪"}, {"id":1993,"name":"德马"}, {"id":9,"name":"盖盖"}, {"id":10,"name":"寝室长"} ], "links":[ {"source":6,"target":1,"type":"籍贯"}, {"source":7,"target":1,"type":"籍贯"}, {"source":5,"target":2,"type":"籍贯"}, {"source":4,"target":3,"type":"籍贯"}, {"source":9,"target":3,"type":"籍贯"}, {"source":1993,"target":0,"type":"籍贯"}, {"source":1993,"target":4,"type":"室友"}, {"source":1993,"target":5,"type":"室友"}, {"source":1993,"target":6,"type":"室友"}, {"source":1993,"target":7,"type":"室友"}, {"source":1993,"target":9,"type":"室友"}, {"source":1993,"target":10,"type":"职位"} ] }
图2后台返回的数据,就是以data.json的样式封装的。
到此,介绍完毕,该进入正题了:
首先在原demo的基础上我参考网上大牛的代码,搭建了自己的demo:
BaseNode:
package com.dbs.common.model; public class BaseNode { }
三个节点实体类:
Coder:
package com.dbs.neo4j.entity.node; import java.util.List; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.Relationship; import com.dbs.common.model.BaseNode; import com.dbs.neo4j.entity.relation.Have; import com.dbs.neo4j.entity.relation.Like; @NodeEntity(label="Coder") public class Coder extends BaseNode { @Id @GeneratedValue private Long id; @Property(name="flagId") private String flagId; @Property(name="name") private String name; @Property(name="sex") private String sex; @Relationship(type="like") private List<Like> like; @Relationship(type="have") private List<Have> have; // getters and setters }
Cat:
package com.dbs.neo4j.entity.node; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import com.dbs.common.model.BaseNode; @NodeEntity(label="Cat") public class Cat extends BaseNode { @Id @GeneratedValue private Long id; @Property(name="flagId") private String flagId; @Property(name="name") private String name; @Property(name="color") private String color; // getters and setters }
Player:
package com.dbs.neo4j.entity.node; import java.util.List; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.Relationship; import com.dbs.common.model.BaseNode; import com.dbs.neo4j.entity.relation.Have; @NodeEntity(label="Player") public class Player extends BaseNode{ @Id @GeneratedValue private Long id; @Property(name="flagId") private String flagId; @Property(name="name") private String name; @Property(name="身高") private double height; @Property(name="职位") private String location; @Relationship(type="have") private List<Have> have; // getters and setters }
两个关系实体类:
Hava:
package com.dbs.neo4j.entity.relation; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.neo4j.ogm.annotation.EndNode; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.RelationshipEntity; import org.neo4j.ogm.annotation.StartNode; import com.dbs.common.model.BaseNode; import com.fasterxml.jackson.annotation.JsonIgnore; @RelationshipEntity(type="have") public class Have { @Id @GeneratedValue private Long id; @Property(name="领养时间") private String date; @JsonIgnore @StartNode private BaseNode startNode; @Property(name="sourceId") private String sourceId; @JsonIgnore @EndNode private BaseNode endNode; @Property(name="targetId") private String targetId; // getters and setters }
Like:
package com.dbs.neo4j.entity.relation; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.neo4j.ogm.annotation.EndNode; import org.neo4j.ogm.annotation.Property; import org.neo4j.ogm.annotation.RelationshipEntity; import org.neo4j.ogm.annotation.StartNode; import com.dbs.common.model.BaseNode; import com.fasterxml.jackson.annotation.JsonIgnore; @RelationshipEntity(type="like") public class Like { @Id @GeneratedValue private Long id; @Property(name="层度") private String degree; @JsonIgnore @StartNode private BaseNode startNode; @Property(name="sourceId") private String sourceId; @JsonIgnore @EndNode private BaseNode endNode; @Property(name="targetId") private String targetId; // getters and setters }
有注解不理解的请参见官方文档。
在此我丰富了人际关系(@RelationshipEntity),上面前端需要的数据data.json你也看到了,其links数组即为这里所谓的人际关系。在原大牛的demo中并没有这些,仅仅因为我前端需要links关系,所以我创建了两个关系实体类。
测试类:
package com.dbs.test.mysql; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.dbs.Neo4jApplication; import com.dbs.neo4j.entity.node.Cat; import com.dbs.neo4j.entity.node.Coder; import com.dbs.neo4j.entity.node.Player; import com.dbs.neo4j.entity.relation.Have; import com.dbs.neo4j.entity.relation.Like; import com.dbs.neo4j.repository.CoderRepository; import com.dbs.utils.UUIDUtils; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Neo4jApplication.class) public class CoderTest { @Autowired CoderRepository coderRepository; @Test public void save() { Coder coder = new Coder(); String coderId = UUIDUtils.getUUID(); coder.setFlagId(coderId); coder.setName("appleyk"); coder.setSex("男"); Player sy = new Player(); String syId = UUIDUtils.getUUID(); sy.setFlagId(syId); sy.setName("孙杨"); sy.setHeight(1.87); sy.setLocation("800米*泳"); Player hd = new Player(); String hdId = UUIDUtils.getUUID(); hd.setFlagId(hdId); hd.setName("哈登"); hd.setHeight(2.17); hd.setLocation("中锋"); //码农的蓝猫 Cat coderlm = new Cat(); String coderlmId = UUIDUtils.getUUID(); coderlm.setFlagId(coderlmId); coderlm.setName("蓝猫"); coderlm.setColor("blue"); //哈登的蓝猫和波斯猫 Cat hdbsm = new Cat(); String hdbsmId = UUIDUtils.getUUID(); hdbsm.setFlagId(hdbsmId); hdbsm.setName("波斯猫"); hdbsm.setColor("white"); Cat hdlm = new Cat(); String hdlmId = UUIDUtils.getUUID(); hdlm.setFlagId(hdlmId); hdlm.setName("蓝猫"); hdlm.setColor("blue"); List<Like> coderLikes = new ArrayList<Like>(); // 喜欢孙杨 Like likesy = new Like(); likesy.setStartNode(coder); likesy.setEndNode(sy); likesy.setDegree("喜欢"); likesy.setSourceId(coder.getFlagId()); likesy.setTargetId(sy.getFlagId()); coderLikes.add(likesy); // 很喜欢哈登 Like likehd = new Like(); likehd.setStartNode(coder); likehd.setEndNode(hd); likehd.setDegree("超喜欢"); likehd.setSourceId(coder.getFlagId()); likehd.setTargetId(hd.getFlagId()); coderLikes.add(likehd); // 码农喜欢的人 coder.setLike(coderLikes); // 码农有一只蓝猫 List<Have> coderHave = new ArrayList<Have>(); Have havelm = new Have(); havelm.setStartNode(coder); // 起始者是Coder havelm.setEndNode(coderlm); havelm.setDate(new Date().toString()); havelm.setSourceId(coder.getFlagId()); havelm.setTargetId(coderlm.getFlagId()); coderHave.add(havelm); coder.setHave(coderHave); // 哈登有一只蓝猫和一只波斯猫 Have hdhavebs = new Have(); hdhavebs.setStartNode(hd); // 起始者是palyer hdhavebs.setEndNode(hdbsm); hdhavebs.setDate(new Date().toString()); hdhavebs.setSourceId(hd.getFlagId()); hdhavebs.setTargetId(hdbsm.getFlagId()); Have hdhavelm = new Have(); hdhavelm.setStartNode(hd); // 起始者是palyer hdhavelm.setEndNode(hdlm); hdhavelm.setDate(new Date().toString()); hdhavelm.setSourceId(hd.getFlagId()); hdhavelm.setTargetId(hdlm.getFlagId()); List<Have> hdHave = new ArrayList<Have>(); // 有两只猫 hdHave.add(hdhavebs);hdHave.add(hdhavelm); hd.setHave(hdHave); coderRepository.save(coder); } }
neo4j提供的数据查询接口:
package com.dbs.neo4j.repository; import java.util.List; import org.springframework.data.neo4j.annotation.Query; import org.springframework.data.neo4j.repository.GraphRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.dbs.neo4j.entity.node.Coder; @Repository public interface CoderRepository extends GraphRepository<Coder> { @Query("MATCH (n:Coder) WHERE n.name = {name} RETURN n LIMIT 1") Coder findByName(@Param("name") String name); @Query("MATCH (a)-[m:like]->(b),(c)-[n:have]->(d) RETURN a,m,b,c,n,d") List<Object> searchAllNode(); }
package com.dbs.neo4j.repository; import org.springframework.data.neo4j.repository.GraphRepository; import org.springframework.stereotype.Repository; import com.dbs.neo4j.entity.node.Player; @Repository public interface PlayerRespository extends GraphRepository<Player> { }
package com.dbs.neo4j.repository; import org.springframework.data.neo4j.repository.GraphRepository; import org.springframework.stereotype.Repository; import com.dbs.neo4j.entity.node.Cat; @Repository public interface CatRespository extends GraphRepository<Cat> { }
生成uuid工具类:
package com.dbs.utils; import java.util.UUID; public class UUIDUtils { public static String getUUID() { String uuid = UUID.randomUUID().toString().replaceAll("-", ""); return uuid; } }
web端控制器:
package com.dbs.neo4j.controller; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.dbs.common.model.ResponseResult; import com.dbs.config.MysqlDataSourceConfig; import com.dbs.neo4j.entity.node.Coder; import com.dbs.neo4j.repository.CoderRepository; /** * 包名:com.dbs.neo4j.controller * 功能:TODO(功能描述) * 作者:hualn * 日期:2018年1月23日 下午3:02:34 */ @Controller @RequestMapping("/coder") public class CoderController { private Logger logger = LoggerFactory.getLogger(MysqlDataSourceConfig.class); @Autowired CoderRepository coderRepositiory; @RequestMapping("/get") public Coder GetCoderByName(@RequestParam(value="name") String name){ return coderRepositiory.findByName(name); } @RequestMapping("/all") @ResponseBody public List<Object> searchAllNode(){ logger.debug("access all"); List<Object> nodes = coderRepositiory.searchAllNode(); if(nodes != null) { return nodes; } logger.info("error,nodes is null"); return null; } @SuppressWarnings("rawtypes") @PostMapping("/save") @Transactional public ResponseResult Create(@RequestBody Coder coder) throws Exception{ Coder result = coderRepositiory.save(coder); if(result!=null){ return new ResponseResult(200,result.getName()+"节点创建成功"); } logger.debug("未查询到数据!"); return new ResponseResult(500,coder.getName()+"节点创建失败!"); } }
前端:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>首页</title> <link th:href="@{plugins/layui/css/layui.css}" rel="stylesheet"/> </head> <body> <form> <input id="getAll_btn" type="button" value="获取所有节点" /> </form> 123 <script th:src="@{plugins/jquery-2.1.1.min.js}" type="text/javascript"></script> <!-- <script th:src="@{plugins/d3/4.12.2/d3.js}" type="text/javascript"></script> --> <script src="https://d3js.org/d3.v5.min.js"></script> <script> $(function() { $("#getAll_btn").on("click",function() { $.ajax({ type:"post", url:"coder/all", data:{}, dataType:"json", success:function(result) { console.log(result); } }); }); }); </script> </body> </html>
逻辑很简单:测试类执行save方法,将数据插入到neo4j数据库,即可在浏览器http://127.0.0.1:7474中看到图1的效果。启动spring-boot,在浏览器中输入http://127.0.0.1:8080即可点击获取所有节点即可在浏览器端控制台看到后台封装的数据。
这是一个完整的demo了,一路踏过的坑,我在此记录一下:
bug1:
No identity field found for class: com.dbs.neo4j.entity.node.Coder; 原因:id不能声明为long,要声明为Long
bug2:
org.springframework.dao.InvalidDataAccessApiUsageException: Error executing Cypher "Neo.ClientError.Statement.ParameterMissing"; Code: Neo.ClientError.Statement.ParameterMissing; Description: Expected parameter(s): name; 原因:使用@Param注解时导入包出错:应该导入import org.springframework.data.repository.query.Param;
bug3:
java.lang.RuntimeException: @StartNode of a relationship entity may not be null 原因:关联的实体类要有明确类型【不能使用object,如果使用公共父类也会导致一个问题就是:展示的图形节点的内容为空】
bug4:
Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: C 原因:springboot查询数据库,如果节点实体中关联多个其它对象,则其会深度查询这些对象。要解决这个问题,可以在关系类中要关联的实体上加@JsonIgnore注解,或者在主实体上加@JsonIgnoreProperties("actor")
bug5:
使用自定义uuid时,没有加上@GraphId注解,导致的问题:后台显示操作成功,但是neo4j里没有插入的库。 现在我即在属性上添加@GraphId注解,又主动的去生成uuid【不让neo4j帮我生成id】,结果还是一样,后台执行是成功了,但是neo4j里还是没有库。 使用@id或@id @GeneratedValue一样,都不能在自己添加id字段
在此着重说一下bug5,原因由来:其实我不想使用官方提供的方式,使用neo4j为我自动生成主键id,我想自定义主键或使用uuid的方式,上午我确实实现了(创建实体类的时候给它一个Long类型的id,比如3L),但是饭后我回来清空了一下neo4j所有库后就不能行了,查阅了并折腾了一下午,官方虽然推荐说让使用uuid,但是并没给出具体使用方式(官方描述):
Do not rely on this ID for long running applications. Neo4j will reuse deleted node ID’s. It is recommended users come up with their own unique identifier for their domain objects (or use a UUID).
网上也鲜曾有人提到过这个问题,但是并无人回应。正如bug5中所说:如果我使用了提供注解的方式在主动去创建uuid,测试虽然执行成功,但是neo4j中并没有数据。如果我不使用任何注解,也是一样的结果。两种注解方式,各种搭配折腾都不行,搞了一下午,网上也没个解决办法,最后我只好放弃了这种方式,如果有哪位大牛会并实现了,请告知在下,在下不胜感激。在此我使用了一种非常low的方式,就是给节点实体类加了一个flagId,其实就是因为我没办法获取neo4j自动生成的id又没办法使用自定义主键uuid,而采取的一种使用属性去记录uuid的方式。在测试类里,你可以看到,我在创建关系时是使用开始节点的flagId与结束节点的flagId进行关联的,这样虽然也实现了我想要的东西,但是让有代码洁癖的我很不爽。后续有时间会继续研究。
写在最后,也是最重要的话:
neo4j的下载安装不在废话,d3的下载使用不在废话。
学习neo4j语法的网站推荐:
https://blog.csdn.net/u010454030/article/details/53131229 【强烈推荐】
https://gitbook.cn/books/5a33782c5778440a9d906017/index.html 【推荐】
学习spring-data-neo4j官网:
https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#preface.additional-resources 【必读】
学习d3.js:
https://blog.csdn.net/qq_34414916/article/details/80026029 (基础教程)
https://blog.csdn.net/lcy132100/article/details/9722543?utm_source=blogxgwz0 (text svg扩展学习)
https://blog.csdn.net/lcy132100/article/details/9722543?utm_source=blogxgwz0 (比例尺 扩展学习)
d3.js制作力导向图:
网上很多,我将在码云上上传两个力导向图的demo
本案例参考大牛网址:
https://www.2cto.com/database/201801/713556.html 【感谢】
其他优秀案例推荐:
https://blog.csdn.net/appleyk/article/details/80290551 【赞】
以上内容都是本人阅读大量网站精选,也非常感谢网上的大牛提供的参考。
后续补充:
前端获取数据使用d3搭建力导向图:
原文地址:https://www.cnblogs.com/liudaihuablogs/p/9839267.html