首先簡單介紹下Neo4j,Neo4j是一個高性能的NOSQL圖形數據庫,它將結構化數據存儲在網絡上而不是表中,它是一個嵌入式的、基於磁盤的、具備完全的事務特性的Java持久化引擎,但是它將結構化數據存儲在網絡(從數學角度叫做圖)而不是表中。Neo4j也可以被看做是一個高性能的圖引擎,該引擎具有成熟數據庫的所有特性。
Neo4j中涉及到幾個關鍵的實體對象,分別是Node(節點)、Relationship(關系)、Path(路徑)、Direction(關系方向)、RelationshipType(關系類型)。朋友們可以將Relationship(關系)看做是連接線,一條連接線每端只能連接一個Node(節點),並且連接線兩端必須同時都連接有Node(節點);Relationship(關系)具有方向和類型特性。Node(節點)可以通過多Relationship(關系)與其他多個Node(節點)關聯,而且Node(節點)也可以是沒有任何連接的孤立節點。Path(路徑)包含多個Node和Relationship,是節點和關系的集合。下圖就是筆者本人利用Neo4j構建的一個“射雕英雄譜”局部關系圖:
3.1. Neo4j數據分頁檢索類型
Neo4j數據分頁檢索接口采用自身的Cypher檢索語句,通過構建Cypher分頁檢索語句,實現分頁處理。
Neo4j數據庫中不存在傳統的表的概念,一個數據庫可以視作一張圖。數據分頁檢索將針對Node和Relationship分別進行,不針對Path進行分頁檢索,因為基本上沒有什么意義。按照檢索條件區分檢索類型,可以細分為以下幾種。
3.1.1 Node(節點)分頁檢索
1) 無條件檢索Cypher語句
--不根據屬性排序 START n=node(*) RETURN n SKIP 0 LIMIT 20 --根據屬性排序 START n=node(*) RETURN n ORDER BY n.NAME DESC SKIP 0 LIMIT 20
2) 根據Property屬性檢索Cypher語句
--根據屬性NAME值進行模糊檢索 START n=node(*) WHERE n.NAME=~'.*tom*' RETURN n SKIP 0 LIMIT 20 --根據屬性NAME值進行精確檢索 START n=node(*) WHERE n.NAME='tom' RETURN n SKIP 0 LIMIT 20
3) 根據Index索引檢索Cypher語句
--說明:N_INDEX為索引名稱,USER_NAME為索引Key名稱 --根據索引值進行模糊檢索 --模糊檢索的多種格式。 --1、*tom*表示USER_NAME中包含tom字符串的 --2、*tom表示USER_NAME右側匹配tom字符串的 --3、tom*表示USER_NAME左側匹配tom字符串的 START n=node:N_INDEX('USER_NAME:*tom*') RETURN n SKIP 0 LIMIT 20 --根據索引值進行精確檢索 START n=node:N_INDEX (USER_NAME='tom') RETURN n SKIP 0 LIMIT 20
4) 根據Index索引和Property屬性檢索Cypher語句
--根據索引(模糊)和屬性(模糊)檢索 START n=node:N_INDEX('USER_NAME:*tom*') WHERE n.USER_TYPE=~'.*sys*' RETURN n SKIP 0 LIMIT 20 --根據索引(模糊)和屬性(精確)檢索 START n=node:N_INDEX('USER_NAME:*tom*') WHERE n.USER_TYPE ='system' RETURN n SKIP 0 LIMIT 20 --根據索引(精確)和屬性(模糊)檢索 START n=node:N_INDEX(USER_NAME='tom') WHERE n.USER_TYPE=~'.*sys*' RETURN n SKIP 0 LIMIT 20 --根據索引(精確)和屬性(精確)檢索 START n=node:N_INDEX(USER_NAME='tom') WHERE n.USER_TYPE ='system' RETURN n SKIP 0 LIMIT 20
5) 根據Label標簽檢索Cypher語句
--標簽內容為”中國” START n=node(*) MATCH (n:中國) RETURN n SKIP 0 LIMIT 20
6) 根據Label標簽和Property屬性檢索Cypher語句
START n=node(*) MATCH (n:中國) WHERE n.USER_TYPE=’system’ RETURN n SKIP 0 LIMIT 20
3.1.2 Relationship(關系)分頁檢索
1) 無條件分頁檢索Cypher語句
--不根據屬性排序 START r=relationship(*) RETURN DISTINCT(r) SKIP 0 LIMIT 20 --根據屬性排序 START r=relationship(*) RETURN DISTINCT(r) ORDER BY r.NAME ASC SKIP 0 LIMIT 20
2) 根據Property屬性檢索Cypher語句
--根據屬性NAME值進行模糊檢索 START r=relationship(*) WHERE r.NAME=~'.*tom*' RETURN r SKIP 0 LIMIT 20 --根據屬性NAME值進行精確檢索 START r=relationship(*) WHERE r.NAME='tom' RETURN r SKIP 0 LIMIT 20
3) 根據Index索引檢索Cypher語句
--說明:R_INDEX為索引名稱,USER_NAME為索引Key名稱 --根據索引值進行模糊檢索 --模糊檢索的多種格式。 --1、*tom*表示USER_NAME中包含tom字符串的 --2、*tom表示USER_NAME右側匹配tom字符串的 --3、tom*表示USER_NAME左側匹配tom字符串的 START r=relationship(*):R_INDEX('USER_NAME:*tom*') RETURN r SKIP 0 LIMIT 20 --根據索引值進行精確檢索 START r=relationship(*):R_INDEX (USER_NAME='tom') RETURN r SKIP 0 LIMIT 20
4) 根據Index索引和Property屬性檢索Cypher語句
--根據索引(模糊)和屬性(模糊)檢索 START r=relationship(*):R_INDEX('USER_NAME:*tom*') WHERE r.USER_TYPE=~'.*sys*' RETURN r SKIP 0 LIMIT 20 --根據索引(模糊)和屬性(精確)檢索 START r=relationship(*):R_INDEX('USER_NAME:*tom*') WHERE r.USER_TYPE ='system' RETURN r SKIP 0 LIMIT 20 --根據索引(精確)和屬性(模糊)檢索 START r=relationship(*):R_INDEX(USER_NAME='tom') WHERE r.USER_TYPE=~'.*sys*' RETURN r SKIP 0 LIMIT 20 --根據索引(精確)和屬性(精確)檢索 START r=relationship(*):R_INDEX(USER_NAME='tom') WHERE r.USER_TYPE ='system' RETURN r SKIP 0 LIMIT 20
5) 根據RelationshipType關系類型檢索Cypher語句
--FRIEND為關系類型字符串 START n=node(*) MATCH n-[r:FRIEND]-() RETURN DISTINCT(r) SKIP 0 LIMIT 20
3.2. Neo4j數據分頁模型類
import java.io.Serializable; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import com.hnepri.common.util.LogInfoUtil; /** * Description: 圖數據庫數據分頁模型類。<br> * 利用此類可分頁管理Node數據和Relationship數據等。 * Copyright: Copyright (c) 2015<br> * Company: 河南電力科學研究院智能電網所<br> * @author shangbingbing 2015-11-01編寫 * @version 1.0 */ public class GraphPageModel implements Serializable { private static final long serialVersionUID = 330410716100946538L; private int pageSize = 10; private int pageIndex = 1; private int prevPageIndex = 1; private int nextPageIndex = 1; private int pageCount = 0; private int pageFirstRowIndex = 1; private boolean hasNextPage = true; private int totalCount = 0; private long startTime = System.currentTimeMillis(); private long endTime = System.currentTimeMillis(); private List<Node> nodeList = new ArrayList<Node>(); private List<Relationship> relationshipList = new ArrayList<Relationship>(); /** * 分頁對象構造函數 * @param pageSize 每頁記錄數 */ public GraphPageModel(int pageSize) { this.pageSize = pageSize; } /** * 獲取分頁記錄數量 * @return */ public int getPageSize() { return pageSize; } /** * 獲取當前頁序號 * @return */ public int getPageIndex() { return pageIndex; } /** * 設置當前頁序號 * @param pageIndex */ public void setPageIndex(int pageIndex) { if(pageIndex <= 0) { pageIndex = 1; } this.pageIndex = pageIndex; } /** * 獲取分頁總數 * @return */ public int getPageCount() { if(this.getTotalCount() == 0) { this.pageCount = 0; } else { int shang = this.getTotalCount() / this.getPageSize(); int yu = this.getTotalCount() % this.getPageSize(); if(yu > 0) { shang += 1; } this.pageCount = shang; } return pageCount; } /** * 獲取每頁的第一行序號 * @return */ public int getPageFirstRowIndex() { this.pageFirstRowIndex = (this.pageIndex - 1) * this.getPageSize() + 1; return pageFirstRowIndex; } /** * 獲取上一頁序號 * @return */ public int getPrevPageIndex() { if(this.pageIndex > 1) { this.prevPageIndex = this.pageIndex - 1; } else { this.prevPageIndex = 1; } return prevPageIndex; } /** * 獲取下一頁序號 * @return */ public int getNextPageIndex() { if(this.pageIndex < this.pageCount) { this.nextPageIndex = this.pageIndex + 1; } else { this.nextPageIndex = this.pageCount; } return nextPageIndex; } /** * 跳轉到下一頁 */ public void nextPage() { if(this.totalCount == 0 || this.getPageCount() == 0) { this.pageIndex = 1; } else { if(this.pageIndex < this.pageCount) { this.pageIndex = this.pageIndex + 1; } else { this.pageIndex = this.pageCount; } } } /** * 跳轉到上一頁 */ public void prevPage() { if(this.pageIndex > 1) { this.pageIndex = this.pageIndex - 1; } else { this.pageIndex = 1; } } /** * 獲取是否有下一頁 * @return */ public boolean isHasNextPage() { if(this.pageIndex < this.getPageCount()) { this.hasNextPage = true; } else { this.hasNextPage = false; } return hasNextPage; } /** * 獲取總記錄數 */ public int getTotalCount() { return totalCount; } /** * 獲取總記錄數 * @param totalCount */ public void setTotalCount(int totalCount) { this.totalCount = totalCount; } /** * 初始化起始時間(毫秒) */ public void initStartTime() { this.startTime = System.currentTimeMillis(); } /** * 初始化截止時間(毫秒) */ public void initEndTime() { this.endTime = System.currentTimeMillis(); } /** * 獲取毫秒格式的耗時信息 * @return */ public String getTimeIntervalByMilli() { return String.valueOf(this.endTime - this.startTime) + "毫秒"; } /** * 獲取秒格式的耗時信息 * @return */ public String getTimeIntervalBySecond() { double interval = (this.endTime - this.startTime)/1000.0; DecimalFormat df = new DecimalFormat("#.##"); return df.format(interval) + "秒"; } /** * 打印時間信息 */ public void printTimeInfo() { LogInfoUtil.printLog("起始時間:" + this.startTime); LogInfoUtil.printLog("截止時間:" + this.endTime); LogInfoUtil.printLog("耗費時間:" + this.getTimeIntervalBySecond()); } /** * 獲取Node檢索結果列表 * @return */ public List<Node> getNodeList() { return nodeList; } /** * 獲取Relationship檢索結果列表 * @return */ public List<Relationship> getRelationshipList() { return relationshipList; } }
3.3. Neo4j數據分頁接口方法
首先,我們先設計一個通用的執行Cypher檢索語句的接口方法,將檢索結果(主要指Node、Relationship和Path對象)封轉進Propertyies列表中。/** * 執行Cypher檢索語句,將檢索結果封裝進Properties列表中。 * @param query cypher檢索語句 * @param params cypher檢索語句參數集合 * @return */ public List<Properties> executeQuery(String query, Map<String,Object> params) { List<Properties> propertiesList = new ArrayList<Properties>(); if(StringUtils.isBlank(query)) { return propertiesList; } ExecutionEngine executionEngine = new ExecutionEngine(this.getGraphDatabaseService()); ExecutionResult result = null; if(params == null || params.size() == 0) { result = executionEngine.execute(query); } else { result = executionEngine.execute(query, params); } for (Map<String, Object> row : result ) { Properties properties = new Properties(); for ( Entry<String, Object> column : row.entrySet()){ properties.put(column.getKey(), column.getValue()); } propertiesList.add(properties); } return propertiesList; }
下面以無條件分頁檢索Node信息為例,講述下接口方法的設計思路。具體代碼如下:
/** * 分頁檢索Node信息。 * @param pageModel 分頁模型對象,不能為空。 * @param orders 排序屬性字段。 * @return */ public GraphPageModel queryNodes(GraphPageModel pageModel, GOrderBy ... orders) { if(pageModel == null) { pageModel = new GraphPageModel(10); } pageModel.getNodeList().clear(); pageModel.getRelationshipList().clear(); //計算總行數 String query = "START n=node(*) RETURN count(*) AS NODE_COUNT"; List<Properties> resultList = this.executeQuery(query); if(resultList == null || resultList.size() == 0) { return pageModel; } for(Properties properties : resultList) { int nodeCount = Integer.valueOf(properties.get("NODE_COUNT").toString()); pageModel.setTotalCount(nodeCount); } //組織排序字段信息 String strGOrderBy = ""; if(orders != null && orders.length > 0) { strGOrderBy = "ORDER BY"; for(GOrderBy order : orders) { strGOrderBy += String.format(" n.%s %s,", order.getPropertyName(), order.getOrderType().toUpperCase()); } strGOrderBy = strGOrderBy.substring(0, strGOrderBy.length() - 1); } int skipCount = (pageModel.getPageIndex() - 1) * pageModel.getPageSize(); int limitCount = pageModel.getPageSize(); query = String.format("START n=node(*) RETURN n AS NODE_ENTRY %s SKIP %s LIMIT %s", strGOrderBy, skipCount, limitCount); List<Properties> list = this.executeQuery(query); for(Properties properties : list) { pageModel.getNodeList().add((Node)properties.get("NODE_ENTRY")); } return pageModel; }
作者:商兵兵
單位:河南省電力科學研究院智能電網所
QQ:52190634