Neo4j图数据分页处理
首先简单介绍下Neo4j,Neo4j是一个高性能的NOSQL图形数据库,它将结构化数据存储在网络上而不是表中,它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做图)而不是表中。Neo4j也可以被看做是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。
Neo4j中涉及到几个关键的实体对象,分别是Node(节点)、Relationship(关系)、Path(路径)、Direction(关系方向)、RelationshipType(关系类型)。朋友们可以将Relationship(关系)看做是连接线,一条连接线每端只能连接一个Node(节点),并且连接线两端必须同时都连接有Node(节点);Relationship(关系)具有方向和类型特性。Node(节点)可以通过多Relationship(关系)与其他多个Node(节点)关联,而且Node(节点)也可以是没有任何连接的孤立节点。Path(路径)包含多个Node和Relationship,是节点和关系的集合。下图就是笔者本人利用Neo4j构建的一个“射雕英雄谱”局部关系图:
neo4j_graph
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;
}
}
模型类中,nodeList和relationshipList分别用来存放Node和Relationship;分页检索Node时,就通过getNodeList()读取Node信息;分页检索Relationship时,就通过getRelationshipList()读取Relationship信息。
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;
}
Neo4j 的详细介绍:请点这里
Neo4j 的下载地址:请点这里
推荐阅读:
Neo4J图数据库实践系列