本項目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搭建力導向圖: