数据分页处理系列之三:Neo4j图数据分页处理

 

首先简单介绍下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;
    }
}

模型类中,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;
}

 



 

作者:商兵兵

单位:河南省电力科学研究院智能电网所

QQ:52190634

主页:http://www.cnblogs.com/shangbingbing

空间:http://shangbingbing.qzone.qq.com

时间: 2024-12-25 19:54:29

数据分页处理系列之三:Neo4j图数据分页处理的相关文章

大数据系列之三:大数据体系架构的重要里程碑

欧凯惯例:引子 世界上唯一不变的就是变化,大数据的架构也不例外. 这次变化的推动者,多是一些大的商业公司! 首发地址 --- Teradata 美国天睿 Teradata这家公司其实挺陌生的,但这并不能让我们忽视其在大数据方面做出的贡献.简单一句描述这家公司的贡献就是: 2008年之前,这家公司以关系型为基础,硬刚大数据,之后意识到数据实在太大大复杂了,终究实现了对非关系型数据的支持. 具体它拿关系型作为对大数据的解决方案硬刚到什么程度呢?拿一个数据说来说明白了,直到2017年,它可以基于其关系

【iOS与EV3混合机器人编程系列之三】编写EV3 Port Viewer 应用监测EV3端口数据

在前两篇文章中,我们对iOS与EV3混合机器人编程做了一个基本的设想,并且介绍了要完成项目所需的软硬件准备和知识准备. 那么在今天这一篇文章中,我们将直接真正开始项目实践. ==第一个项目: EV3 Port Viewer== 项目目的:在iOS设备上通过WiFi连接EV3并且读取EV3每个端口的数据. 大家可以一周之后在App Store上搜索EV3 Port Viewer,那么我已经做了一个范例App发布了,正在审核中 应用的基本使用要求:将EV3和iPhone同时连接到同一个WiFi网络中

数据分页处理系列之一:Oracle表数据分页检索SQL

  关于Oracle数据分页检索SQL语法,网络上比比皆是,花样繁多,本篇也是笔者本人在网络上搜寻的比较有代表性的语法,绝非本人原创,贴在这里,纯粹是为了让"数据分页专题系列"看起来稍微完整和丰满一些,故先在这里特别声明一下,以免招来骂声一片! 先介绍两个比较有代表性的数据分页检索SQL实例. 无ORDER BY排序的写法.(效率最高) (经过测试,此方法成本最低,只嵌套一层,速度最快!即使检索的数据量再大,也几乎不受影响,速度依然!) SELECT * FROM (SELECT RO

数据分页模块系列 (二) 完美封装PageModel实现分页模块

先说一下框架分页技术,在我们之前那个高校项目中使用DWZ实现的分页用了自定义标签使用起来也比较方便,除了DWZ很多框架已经给我们做好了分页我们需要做的仅仅是把一些分页参数传给我们的框架,俗话说你用别人的东西就得按着别人的来,得按着别人的框架进行布局了.传参了等等,这样灵活性大大降低,况且感觉使用框架频繁的一些软件一般是一些管理类型的,对界面的美观方便需求并不是很高,工整.能用就可以,然而呢,还有些界面需要灵活配置分页按钮.分页条等,这就需要把类似于这样的功能封装起来. 分页的模块很多人都在写都在

使用neo4j图数据库的import工具导入数据 -方法和注意事项

背景 最近我在尝试存储知识图谱的过程中,接触到了Neo4j图数据库,这里我摘取了一段Neo4j的简介: Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中.它是一个嵌入式的.基于磁盘的.具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做图)上而不是表中.Neo4j也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性.程序员工作在一个面向对象的.灵活的网络结构下而不是严格.静态的表中--但是他们可以享受到具备完全的事务

网络相关系列之三:通过GET和POST方法发送数据

写在最前面:年少的安逸舒适在随着年龄的到来和现实生活的压迫总有一天会全数归还(其实就是<无间道>中那句:"出来混,迟早要还的!") so fighting! 一.GET和POST的对比: 在漫长的时间当中,其他的方法逐渐的退出了历史舞台,最常用的只剩下GET和POST方法.而之前已经讲过了通过GET方法获取数据,今天来学习一下如何分别通过GET和POST获取数据. 举个例子:get类似于明信片,只有请求头,没有请求体.而post类似于一封信,信封上的内容为请求头:信里面的内

「数据治理那点事」系列之三:不忘初心方得始终,数据质量管理要稳住!

文 | 乐天(原创)未经许可,不可转载. 编辑 | 刘能 碳酸 本文约 3209 字,预计阅读时间 9 分钟 作者简介:蒋珍波(乐天),6 年+ 大数据咨询经验,擅长为客户提供科学合理的大数据解决方案.目前担任数澜科技咨询专家,负责数澜大数据平台售前咨询. 本文主要讲述数据治理中的重要工作:数据质量管理,从以下几个角度展开具体讲解: (1)数据质量管理的目标 (2)质量问题产生的根源 (3)数据质量的评估 (4)数据质量管理的流程 (5)数据质量管理的取舍 一.数据质量管理的目标 数据质量管理主

【PPT&amp;视频】《陈新河:万亿元大数据产业新生态》——央视网大数据名人讲堂之大数据产业系列

[PPT&视频]<陈新河:万亿元大数据产业新生态>--央视网大数据名人讲堂之大数据产业系列 原创 2016-07-16 陈新河 软件定义世界(SDX) 热门下载(点击标题即可阅读) ?[下载]2015中国数据分析师行业峰会精彩PPT下载(共计21个文件) 因微信限制,部分图不能显示出来,高清完整版全文请扫描二维码,见每篇文章底部专栏 <陈新河:万亿元大数据产业新生态>--央视网大数据名人讲堂之大数据产业系列 嘉宾介绍 陈新河   中关村大数据产业联盟副秘书长 Talking

Titan图数据介绍

Titan是一个基于图的数据库.他同样属于现在比较热火的NoSQL中的一类.使用Titan的基本业务场景就是构建关系图谱.相比于Titan数据库,我们可能更加熟悉Neo4j这个数据库.Neo4j也是一款图数据,切应用范围也相当广泛.在比较过两款数据库后,Titan成为了我们选择.主要有以下几点考量: 1:Titan数据库能够存储的数据节点规模更加庞大 2:在大规模数据量下,Titan的性能不受单机物理性能影响 3:Titan是分布式.高可用,开源且有公司在维护 Titan官网地址:http://