Neo4j 是一个高性能的 NoSQL 图形数据库。Neo4j 使用图(graph)相关的概念来描述数据模型,把数据保存为图中的节点以及节点之间的关系。很多应用中数据之间的关系,可以很直接地使用图中节点和关系的概念来建模。对于这样的应用,使用 Neo4j 来存储数据会非常的自然,要优于使用关系数据库。本文对 Neo4j 进行了深入的介绍,并结合具体的实例来进行详细的说明,可以让您对 Neo4j 有深入的了解,从而可以在应用开发中恰当地选择 Neo4j 来作为存储方式。

成 富, 高级软件工程师

2013 年 6 月 20 日

  • expand内容

Neo4j 简介

数据存储一般是应用开发中不可或缺的组成部分。应用运行中产生的和所需要的数据被以特定的格式持久化下来。应用开发中很常见的一项任务是在应用本身的领域对象模型与数据存储格式之间进行相互转换。如果数据存储格式与领域对象模型之间比较相似,那么进行转换所需的映射关系更加自然,实现起来也更加容易。对于一个特定的应用来说,其领域对象模型由应用本身的特征来决定,一般采用最自然和最直观的方式来进行建模。所以恰当的选择数据存储格式就显得很重要。目前最常见的数据存储格式是关系数据库。关系数据库通过实体 - 关系模型(E-R 模型)来进行建模,即以表和表之间的关系来建模。在实际开发中可以使用的关系数据库的实现非常多,包括开源的和商用的。关系数据库适合用来存储数据条目的类型同构的表格型数据。如果领域对象模型中不同对象之间的关系比较复杂,则需要使用繁琐的对象关系映射技术(Object-Relationship Mapping,ORM)来进行转换。

对于很多应用来说,其领域对象模型并不适合于转换成关系数据库形式来存储。这也是非关系型数据库(NoSQL)得以流行的原因。NoSQL 数据库的种类很多,包括键值对数据库、面向文档数据库和图形数据库等。本文中介绍的 Neo4j 是最重要的图形数据库。Neo4j 使用数据结构中图(graph)的概念来进行建模。Neo4j 中两个最基本的概念是节点和边。节点表示实体,边则表示实体之间的关系。节点和边都可以有自己的属性。不同实体通过各种不同的关系关联起来,形成复杂的对象图。Neo4j 同时提供了在对象图上进行查找和遍历的功能。

对于很多应用来说,其中的领域对象模型本身就是一个图结构。对于这样的应用,使用 Neo4j 这样的图形数据库进行存储是最适合的,因为在进行模型转换时的代价最小。以基于社交网络的应用为例,用户作为应用中的实体,通过不同的关系关联在一起,如亲人关系、朋友关系和同事关系等。不同的关系有不同的属性。比如同事关系所包含的属性包括所在公司的名称、开始的时间和结束的时间等。对于这样的应用,使用 Neo4j 来进行数据存储的话,不仅实现起来简单,后期的维护成本也比较低。

Neo4j 使用“图”这种最通用的数据结构来对数据进行建模使得 Neo4j 的数据模型在表达能力上非常强。链表、树和散列表等数据结构都可以抽象成用图来表示。Neo4j 同时具有一般数据库的基本特性,包括事务支持、高可用性和高性能等。Neo4j 已经在很多生产环境中得到了应用。流行的云应用开发平台 Heroku 也提供了 Neo4j 作为可选的扩展。

在简要介绍完 Neo4j 之后,下面介绍 Neo4j 的基本用法。

Neo4j 基本使用

在使用 Neo4j 之前,需要首先了解 Neo4j 中的基本概念。

节点和关系

Neo4j 中最基本的概念是节点(node)和关系(relationship)。节点表示实体,由 org.neo4j.graphdb.Node 接口来表示。在两个节点之间,可以有不同的关系。关系由 org.neo4j.graphdb.Relationship 接口来表示。每个关系由起始节点、终止节点和类型等三个要素组成。起始节点和终止节点的存在,说明了关系是有方向,类似于有向图中的边。不过在某些情况,关系的方向可能并没有意义,会在处理时被忽略。所有的关系都是有类型的,用来区分节点之间意义不同的关系。在创建关系时,需要指定其类型。关系的类型由org.neo4j.graphdb.RelationshipType 接口来表示。节点和关系都可以有自己的属性。每个属性是一个简单的名值对。属性的名称是String 类型的,而属性的值则只能是基本类型、String 类型以及基本类型和 String 类型的数组。一个节点或关系可以包含任意多个属性。对属性进行操作的方法声明在接口 org.neo4j.graphdb.PropertyContainer 中。Node 和 Relationship 接口都继承自PropertyContainer 接口。PropertyContainer 接口中常用的方法包括获取和设置属性值的 getProperty 和 setProperty。下面通过具体的示例来说明节点和关系的使用。

该示例是一个简单的歌曲信息管理程序,用来记录歌手、歌曲和专辑等相关信息。在这个程序中,实体包括歌手、歌曲和专辑,关系则包括歌手与专辑之间的发布关系,以及专辑与歌曲之间的包含关系。清单 1 给出了使用 Neo4j 对程序中的实体和关系进行操作的示例。

清单 1. 节点和关系的使用示例
  private static enum RelationshipTypes implements RelationshipType { PUBLISH, CONTAIN } public void useNodeAndRelationship() { GraphDatabaseService db = new EmbeddedGraphDatabase("music"); Transaction tx = db.beginTx(); try { Node node1 = db.createNode(); node1.setProperty("name", "歌手 1"); Node node2 = db.createNode(); node2.setProperty("name", "专辑 1"); node1.createRelationshipTo(node2, RelationshipTypes.PUBLISH); Node node3 = db.createNode(); node3.setProperty("name", "歌曲 1"); node2.createRelationshipTo(node3, RelationshipTypes.CONTAIN); tx.success(); } finally { tx.finish(); } }

在 清单 1 中,首先定义了两种关系类型。定义关系类型的一般做法是创建一个实现了 RelationshipType 接口的枚举类型。RelationshipTypes 中的 PUBLISH 和 CONTAIN 分别表示发布和包含关系。在 Java 程序中可以通过嵌入的方式来启动 Neo4j 数据库,只需要创建 org.neo4j.kernel.EmbeddedGraphDatabase 类的对象,并指定数据库文件的存储目录即可。在使用 Neo4j 数据库时,进行修改的操作一般需要包含在一个事务中来进行处理。通过 GraphDatabaseService 接口的 createNode 方法可以创建新的节点。Node 接口的 createRelationshipTo 方法可以在当前节点和另外一个节点之间创建关系。

另外一个与节点和关系相关的概念是路径。路径有一个起始节点,接着的是若干个成对的关系和节点对象。路径是在对象图上进行查询或遍历的结果。Neo4j 中使用 org.neo4j.graphdb.Path 接口来表示路径。Path 接口提供了对其中包含的节点和关系进行处理的一些操作,包括 startNode 和 endNode 方法来获取起始和结束节点,以及 nodes 和 relationships 方法来获取遍历所有节点和关系的 Iterable 接口的实现。关于图上的查询和遍历,在下面小节中会进行具体的介绍。

使用索引

当 Neo4j 数据库中包含的节点比较多时,要快速查找满足条件的节点会比较困难。Neo4j 提供了对节点进行索引的能力,可以根据索引值快速地找到相应的节点。清单 2 给出了索引的基本用法。

清单 2. 索引的使用示例
 public void useIndex() { GraphDatabaseService db = new EmbeddedGraphDatabase("music"); Index<Node> index = db.index().forNodes("nodes"); Transaction tx = db.beginTx(); try { Node node1 = db.createNode(); String name = "歌手 1"; node1.setProperty("name", name); index.add(node1, "name", name); node1.setProperty("gender", "男"); tx.success(); } finally { tx.finish(); } Object result = index.get("name", "歌手 1").getSingle() .getProperty("gender"); System.out.println(result); // 输出为“男”}

在 清单 2 中,通过 GraphDatabaseService 接口的 index 方法可以得到管理索引的 org.neo4j.graphdb.index.IndexManager 接口的实现对象。Neo4j 支持对节点和关系进行索引。通过 IndexManager 接口的 forNodes 和 forRelationships 方法可以分别得到节点和关系上的索引。索引通过 org.neo4j.graphdb.index.Index 接口来表示,其中的 add 方法用来把节点或关系添加到索引中,get 方法用来根据给定值在索引中进行查找。

图的遍历

在图上进行的最实用的操作是图的遍历。通过遍历操作,可以获取到与图中节点之间的关系相关的信息。Neo4j 支持非常复杂的图的遍历操作。在进行遍历之前,需要对遍历的方式进行描述。遍历的方式的描述信息由下列几个要素组成。

  • 遍历的路径:通常用关系的类型和方向来表示。
  • 遍历的顺序:常见的遍历顺序有深度优先和广度优先两种。
  • 遍历的唯一性:可以指定在整个遍历中是否允许经过重复的节点、关系或路径。
  • 遍历过程的决策器:用来在遍历过程中判断是否继续进行遍历,以及选择遍历过程的返回结果。
  • 起始节点:遍历过程的起点。

Neo4j 中遍历方式的描述信息由 org.neo4j.graphdb.traversal.TraversalDescription 接口来表示。通过 TraversalDescription 接口的方法可以描述上面介绍的遍历过程的不同要素。类 org.neo4j.kernel.Traversal 提供了一系列的工厂方法用来创建不同的 TraversalDescription 接口的实现。清单 3中给出了进行遍历的示例。

清单 3. 遍历操作的示例
 TraversalDescription td = Traversal.description() .relationships(RelationshipTypes.PUBLISH) .relationships(RelationshipTypes.CONTAIN) .depthFirst() .evaluator(Evaluators.pruneWhereLastRelationshipTypeIs(RelationshipTypes.CONTAIN)); Node node = index.get("name", "歌手 1").getSingle(); Traverser traverser = td.traverse(node); for (Path path : traverser) { System.out.println(path.endNode().getProperty("name")); }

在 清单 3 中,首先通过 Traversal 类的 description 方法创建了一个默认的遍历描述对象。通过 TraversalDescription 接口的 relationships 方法可以设置遍历时允许经过的关系的类型,而 depthFirst 方法用来设置使用深度优先的遍历方式。比较复杂的是表示遍历过程的决策器的 evaluator 方法。该方法的参数是 org.neo4j.graphdb.traversal.Evaluator 接口的实现对象。Evalulator 接口只有一个方法 evaluate。evaluate 方法的参数是 Path 接口的实现对象,表示当前的遍历路径,而 evaluate 方法的返回值是枚举类型 org.neo4j.graphdb.traversal.Evaluation,表示不同的处理策略。处理策略由两个方面组成:第一个方面为是否包含当前节点,第二个方面为是否继续进行遍历。Evalulator 接口的实现者需要根据遍历时的当前路径,做出相应的决策,返回适当的 Evaluation 类型的值。类 org.neo4j.graphdb.traversal.Evaluators 提供了一些实用的方法来创建常用的 Evalulator 接口的实现对象。清单 3 中使用了 Evaluators 类的 pruneWhereLastRelationshipTypeIs 方法。该方法返回的 Evalulator 接口的实现对象会根据遍历路径的最后一个关系的类型来进行判断,如果关系类型满足给定的条件,则不再继续进行遍历。

清单 3 中的遍历操作的作用是查找一个歌手所发行的所有歌曲。遍历过程从表示歌手的节点开始,沿着 RelationshipTypes.PUBLISH 和 RelationshipTypes.CONTAIN 这两种类型的关系,按照深度优先的方式进行遍历。如果当前遍历路径的最后一个关系是 RelationshipTypes.CONTAIN 类型,则说明路径的最后一个节点包含的是歌曲信息,可以终止当前的遍历过程。通过 TraversalDescription 接口的 traverse 方法可以从给定的节点开始遍历。遍历的结果由 org.neo4j.graphdb.traversal.Traverser 接口来表示,可以从该接口中得到包含在结果中的所有路径。结果中的路径的终止节点就是表示歌曲的实体。

Neo4j 实战开发

在介绍了 Neo4j 的基本使用方式之后,下面通过具体的案例来说明 Neo4j 的使用。作为一个数据库,Neo4j 可以很容易地被使用在 Web 应用开发中,就如同通常使用的 MySQL、SQL Server 和 DB2 等关系数据库一样。不同之处在于如何对应用中的数据进行建模,以适应后台存储方式的需求。同样的领域模型,既可以映射为关系数据库中的 E-R 模型,也可以映射为图形数据库中的图模型。对于某些应用来说,映射为图模型更为自然,因为领域模型中对象之间的各种关系会形成复杂的图结构。

本节中使用的示例是一个简单的微博应用。在微博应用中,主要有两种实体,即用户和消息。用户之间可以互相关注,形成一个图结构。用户发布不同的微博消息。表示微博消息的实体也是图中的一部分。从这个角度来说,使用 Neo4j 这样的图形数据库,可以更好地描述该应用的领域模型。

如同使用关系数据库一样,在使用 Neo4j 时,既可以使用 Neo4j 自身的 API,也可以使用第三方框架。Spring 框架中的 Spring Data 项目提供了对 Neo4j 的良好支持,可以在应用开发中来使用。Spring Data 项目把 Neo4j 数据库中的 CRUD 操作、使用索引和进行图的遍历等操作进行了封装,提供了更加抽象易用的 API,并通过使用注解来减少开发人员所要编写的代码量。示例的代码都是通过 Spring Data 来使用 Neo4j 数据库的。下面通过具体的步骤来介绍如何使用 Spring Data 和 Neo4j 数据库。

开发环境

使用 Neo4j 进行开发时的开发环境的配置比较简单。只需要根据 参考资源中给出的地址,下载 Neo4j 本身的 jar 包以及所依赖的 jar 包,并加到 Java 程序的 CLASSPATH 中就可以了。不过推荐使用 Maven 或 Gradle 这样的工具来进行 Neo4j 相关依赖的管理。

定义数据存储模型

前面已经提到了应用中有两种实体,即用户和消息。这两种实体需要定义为对象图中的节点。清单 1 中给出的创建实体的方式并不直观,而且并没有专门的类来表示实体,后期维护成本比较高。Spring Data 支持在一般的 Java 类上添加注解的方式来声明 Neo4j 中的节点。只需要在 Java 类上添加 org.springframework.data.neo4j.annotation.NodeEntity 注解即可,如 清单 4 所示。

清单 4. 使用 NodeEntity 注解声明节点类
  @NodeEntity public class User { @GraphId Long id; @Indexed String loginName; String displayName; String email; }

如 清单 4 所示,User 类用来表示用户,作为图中的节点。User 类中的域自动成为节点的属性。注解 org.springframework.data.neo4j.annotation.GraphId 表明该属性作为实体的标识符,只能使用 Long 类型。注解 org.springframework.data.neo4j.annotation.Indexed 表明为属性添加索引。

节点之间的关系同样用注解的方式来声明,如 清单 5 所示。

清单 5. 使用 RelationshipEntity 注解声明关系类
 @RelationshipEntity(type = "FOLLOW") public class Follow { @StartNode User follower; @EndNode User followed; Date followingDate = new Date(); }

在 清单 5 中,RelationshipEntity 注解的属性 type 表示关系的类型,StartNode 和 EndNode 注解则分别表示关系的起始节点和终止节点。

在表示实体的类中也可以添加对关联的节点的引用,如 清单 6 中给出的 User 类中的其他域。

清单 6. User 类中对关联节点的引用
 @RelatedTo(type = "FOLLOW", direction = Direction.INCOMING) @Fetch Set<User> followers = new HashSet<User>(); @RelatedTo(type = "FOLLOW", direction = Direction.OUTGOING) @Fetch Set<User> followed = new HashSet<User>(); @RelatedToVia(type = "PUBLISH") Set<Publish> messages = new HashSet<Publish>();

如 清单 6 所示,注解 RelatedTo 表示与当前节点通过某种关系关联的节点。因为关系是有向的,可以通过 RelatedTo 的 direction 属性来声明关系的方向。对当前用户节点来说,如果 FOLLOW 关系的终止节点是当前节点,则说明关系的起始节点对应的用户是当前节点对应的用户的粉丝,用“direction = Direction.INCOMING”来表示。因此 followers 域表示的是当前用户的粉丝的集合,而 followed 域表示的是当前用户所关注的用户的集合。注解 RelatedToVia 和 RelatedTo 的作用类似,只不过 RelatedToVia 不关心关系的方向,只关心类型。因此 messages 域包含的是当前用户所发布的消息的集合。

数据操作

在定义了数据存储模型之后,需要创建相应的类来对数据进行操作。数据操作的对象是数据模型中的节点和关系类的实例,所涉及的操作包括常见的 CRUD,即创建、读取、更新和删除,还包括通过索引进行的查找和图上的遍历操作等。由于这些操作的实现方式都比较类似,Spring Data 对这些操作进行了封装,提供了简单的使用接口。Spring Data 所提供的数据操作核心接口是 org.springframework.data.neo4j.repository.GraphRepository。GraphRepository 接口继承自三个提供不同功能的接口:org.springframework.data.neo4j.repository.CRUDRepository 接口提供 save、delete、findOne 和 findAll 等方法,用来进行基本的 CRUD 操作;org.springframework.data.neo4j.repository.IndexRepository 则提供了 findByPropertyValue、findAllByPropertyValue 和 findAllByQuery 等方法,用来根据索引来查找;org.springframework.data.neo4j.repository.TraversalRepository 则提供了 findAllByTraversal 方法,用来根据 TraversalDescription 接口的描述来进行遍历操作。

Spring Data 为 GraphRepository 接口提供了默认的实现。在大多数情况下,只需要声明一个接口继承自 GraphRepository 接口即可,Spring Data 会在运行时创建相应的实现类的对象。对表示用户的节点类 User 进行操作的接口 UserRepository 如 清单 7 所示。

清单 7. 操作 User 类的 UserRepository 接口
 public interface UserRepository extends GraphRepository<User> { }

如 清单 7 所示,UserRepository 接口继承自 GraphRepository 接口,并通过泛型声明要操作的是 User 类。对节点类的操作比较简单,而对于关系类的操作就相对复杂一些。清单 8 中给出了对发布关系进行操作的接口 PublishRepository 的实现。

清单 8. 操作 Publish 类的 PublishRepository 接口
 public interface PublishRepository extends GraphRepository<Publish> { @Query("start user1=node({0}) " + " match user1-[:FOLLOW]->user2-[r2:PUBLISH]->followedMessage" + " return r2") List<Publish> getFollowingUserMessages(User user); @Query("start user=node({0}) match user-[r:PUBLISH]->message return r") List<Publish> getOwnMessages(User user); }

在 清单 8 中,getFollowingUserMessages 方法用来获取某个用户关注的所有其他用户所发布的消息。该方法的实现是通过图上的遍历操作来完成的。Spring Data 提供了一种简单的查询语言来描述遍历操作。通过在方法上添加 org.springframework.data.neo4j.annotation.Query 注解来声明所使用的遍历方式即可。以 getFollowingUserMessages 方法的遍历声明为例,“node({0})”表示当前节点,“start user1=node({0})”表示从当前节点开始进行遍历,并用 user1 表示当前节点。“match”用来表示遍历时选中的节点应该满足的条件。条件“user1-[:FOLLOW]->user2-[r2:PUBLISH]->followedMessage”中,先通过类型为 FOLLOW 的关系找到 user1 所关注的用户,以 user2 来表示;再通过类型为 PUBLISH 的关系,查找 user2 所发布的消息。“return”用来返回遍历的结果,r2 表示的是类型为 PUBLISH 的关系,与 getFollowingUserMessages 方法的返回值类型 List<Publish> 相对应。

在应用中的使用

在定义了数据操作的接口之后,就可以在应用的服务层代码中使用这些接口。清单 9 中给出了用户发布新微博时的操作方法。

清单 9. 用户发布新微博的方法
 @Autowired UserRepository userRepository; @Transactional public void publish(User user, String content) { Message message = new Message(content); messageRepository.save(message); user.publish(message); userRepository.save(user); }

如 清单 9 所示,publish 方法用来给用户 user 发布内容为 content 的微博。域 userRepository 是 UserRepository 接口的引用,由 Spring IoC 容器在运行时自动注入依赖,该接口的具体实现由 Spring Data 提供。在 publish 方法中,首先创建一个 Message 实体类的对象,表示消息节点;再通过 save 方法把该节点保存到数据库中。User 类的 publish 方法的实现如 清单 10 所示,其逻辑是创建一个 Publish 类的实例表示发布关系,并建立用户和消息实体之间的关系。最后再更新 user 对象即可。

清单 10. User 类的 publish 方法
 @RelatedToVia(type = "PUBLISH") Set<Publish> messages = new HashSet<Publish>(); public Publish publish(Message message) { Publish publish = new Publish(this, message); this.messages.add(publish); return publish; }

在创建了相关的服务层类之后,就可以从服务层中暴露出相关的使用 JSON 的 REST 服务,然后在 REST 服务的基础上创建应用的前端展示界面。界面的实现部分与 Neo4j 并无关系,在这里不再赘述。整个程序基于 Spring 框架来开发。Spring Data 为 Neo4j 提供了独立的配置文件名称空间,可以方便在 Spring 配置文件中对 Neo4j 进行配置。清单 11 给出了与 Neo4j 相关的 Spring 配置文件。

清单 11. Neo4j 的 Spring 配置文件
 <neo4j:config storeDirectory="data/neo-mblog.db" /> <neo4j:repositories base-package="com.chengfu.neomblog.repository" />

在 清单 11 中,config 元素用来设置 Neo4j 数据库的数据保存目录,repositories 元素用来声明操作 Neo4j 中的节点和关系类的 GraphRepository 接口的子接口的包名。Spring Data 会负责在运行时扫描该 Java 包,并为其中包含的接口创建出对应的实现对象。

示例应用的完整代码存放在 GitHub 上,见 参考资源。

使用 Neo4j 原生 API

如果不使用 Spring Data 提供的 Neo4j 支持,而使用 Neo4j 的原生 API,也是一样可以进行开发。只不过由于 Neo4j 的原生 API 的抽象层次较低,使用起来不是很方便。下面以示例应用中用户发布微博的场景来展示原生 API 的基本用法,见 清单 12。

清单 12. 使用 Neo4j 原生 API
 public void publish(String username, String message) { GraphDatabaseService db = new EmbeddedGraphDatabase("mblog"); Index<Node> index = db.index().forNodes("nodes"); Node ueserNode = index.get("user-loginName", username).getSingle(); if (ueserNode != null){ Transaction tx = db.beginTx(); try { Node messageNode = db.createNode(); messageNode.setProperty("message", message); ueserNode.createRelationshipTo(messageNode, RelationshipTypes.PUBLISH); tx.success(); } finally { tx.finish(); } } }

从 清单 12 中可以看出,原生 API 的基本用法是先通过 Neo4j 数据库的索引找到需要操作的表示用户的节点,然后再创建出表示微博消息的节点,最后在两个节点之间建立关系。这些步骤都使用 Neo4j 的基本 API 来完成。

与 清单 10 中使用 Spring Data 的功能相同的方法进行比较,可以发现使用原生 API 的代码要复杂不少,而使用 Spring Data 的则简洁很多。因此,在实际开发中推荐使用 Spring Data。

小结

关系数据库在很长一段时间都是大多数应用采用的数据存储方式的首要选择。随着技术的发展,越来越多的 NoSQL 数据库开始流行起来。对于应用开发人员来说,不应该总是盲目使用关系数据库,而是要根据应用本身的特点,选用最合适的存储方式。Neo4j 数据库以“图”作为数据之间关系的描述方式,非常适合于使用在数据本身就以图结构来组织的应用中。本文对 Neo4j 数据库的使用做了详细的介绍,可以帮助开发人员了解和使用 Neo4j 数据库。

1 应用开发概述

基于数据传输效率以及接口自定义等特殊性需求,我们暂时放弃使用Neo4j服务器版本,而是在Neo4j嵌入式版本的基础上进行一些封装性的开发。封装的重点,是解决Neo4j嵌入式版本EmbeddedGraphDatabase中不能同时创建多个实例指向同一个数据库的问题。如果开发人员使用Neo4j嵌入式版本作为数据库,要想实现多个程序共享一个数据库,这将是一个不可回避的问题。本手册给出的解决方案是“构建一个中间服务层,提供各种接口方法,指向同一个数据库实例;其他客户端程序通过中间服务层与Neo4j嵌入式数据库进行通信”。因为我们已经从Neo4j官方声明中得知:Neo4j嵌入式实例可以在多个线程中共享。

系统框架如下图所示:

系统架构图

Neo4j Java 工具包

Neo4j Java 工具包是对Neo4j嵌入式版本Java API的二次封装,根据业务类型划分为Node(Relationship)、Index、Path和Cypher等四种工具集。

管理工具Server端

之所以称其为Server端,是因为其中包含了对RMI Server的管理。此管理工具的主要功能包括图数据库信息管理(包括新建、删除、统计图数据库等)、图数据库数据管理(包括Node数据管理、Relationship数据管理等)、数据导入导出(包括Neo4j与oracle、mysql和excel等之间的数据转换)、RMI Server监控管理等。

管理工具Server端只能部署在Neo4j数据库服务器上,并且只能部署一套程序,否则将违背Neo4j图数据库单例的原则。

RMI Service(服务)

RMI Server(服务),是在Neo4j Java 工具包的基础上,设计的一套接口服务,分为Server端和Client端。Server端用于接口方法的实现和监控管理,Client端用于接口方法的定义和分发(供其他外部系统使用)。总体设计思路,是将Neo4j中的关键对象(Node、Relationship、Path、Direction等)进行可序列化的封装,通过远程调用服务返回给客户端使用。

管理工具Client端

管理工具Client端,是基于RMI Client设计的Neo4j数据管理工具,主要功能包括图数据库信息查看功能、图数据库数据管理功能、数据导入导出功能等。管理工具Client端可以部署多套。

2 Neo4j Java 工具包

以上所有功能(工具包、RMI Service以及管理工具)都包含在两个Java项目中,项目结构如下图所示:

其中,下图为各个工具类之

hnepri-neo4j-common为工具包项目,包括Neo4j Java 工具包、RMI Server端和管理工具Server端;hnepri-neo4j-client为客户端项目,包括RMI Client端和管理工具Client端。

下面主要介绍Neo4j Java 工具包的几个封装关键点,其中,下图为各个工具类之间的关联效果图。

2.1 Node操作工具类(GraphNodeUtil)

Node操作工具类的主要功能包括Node节点和Relationship关系的创建、编辑、删除、查询,以及Label和Property的管理等。对于其中的部分接口方法,根据实际情况和需要则进行了事务处理。相关接口方法截图如下所示:

2.2 Index操作工具类(GraphIndexUtil)

Index操作工具类的主要功能包括Node和Relationship相关索引信息的创建、编辑、删除、查询,以及基于索引查询Node节点和Relationship关系等。对于其中的部分接口方法,根据实际情况和需要则进行了事务处理。相关接口方法截图如下所示:

2.3 Path操作工具类(GraphPathUtil)

Path操作工具类的主要功能包括针对Path的检索操作,包括路径深度遍历、两点之间路径寻址等接口方法。对于其中的部分接口方法,根据实际情况和需要则进行了事务处理。相关接口方法截图如下所示:

2.4 Cypher操作工具类(GraphCypherUtil)

Cypher操作工具类是对Cypher查询语言的封装,主要包括针对Node、Relationship和Path的自定义查询操作。另外,也包括了多种方式的分页查询。对于其中的部分接口方法,根据实际情况和需要则进行了事务处理。

其中,最基本的接口方法是executeQuery方法,执行Cypher查询语句,将查询结果以Properties的形式存在在List中,然后再由其他接口方法显式地转换为Node、Relationship、Path或者其他基本类型使用。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1  /**
 2* 执行Cypher查询语句,将检索结果封装进Properties列表中。
 3* @param query cypher查询语句
 4* @param params cypher查询语句参数集合
 5* @return
 6*/
 7     public List<Properties> executeQuery(String query, Map<String,Object> params) {
 8   GraphTimerModel timer = GraphTimerModel.create();
 9   List<Properties> propertiesList = new ArrayList<Properties>();
10   if(StringUtils.isBlank(query)) {
11 return propertiesList;
12   }
13   ExecutionResult result = null;
14   if(params == null || params.size() == 0) {
15 result = this.getExecutionEngine().execute(query);
16   } else {
17 result = this.getExecutionEngine().execute(query, params);
18   }
19   LogInfoUtil.printLog(query);
20  
21   for (Map<String, Object> row : result ) {
22 Properties properties = new Properties();
23 for ( Entry<String, Object> column : row.entrySet()){
24     properties.put(column.getKey(), column.getValue());
25 }
26 propertiesList.add(properties);
27   }
28   timer.initEndTime();
29   timer.printTimeInfo();
30   return propertiesList;
31     }
32     举例如下所示:
33     String query = "START n=node(*) WHERE n.name=’tom’ RETURN n AS NODE_ENTRY";
34     List<Properties> list = this.executeQuery(query);
35     for(Properties properties : list) {
36   nodeList.add((Node)properties.get("NODE_ENTRY"));
37     }

相关接口方法截图如下所示:

2.5 动态生成RelationshipType(GraphRelTypeUtil)

动态生成RelationshipType是构建RMI服务必须首先要解决的一个关键点,因为RMI要求Server端与Client端之间的传输对象必须是可序列化的对象,而Neo4j API中的接口类和枚举是无法真正序列化的,这也是我们在RMI Service中对相关实体进行封装的根本原因。

所谓动态生成RelationshipType,就是可根据字符串类型的关系类型,生成符合Neo4j Java API要求的枚举类型RelationshipType。关键代码如下表所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
  1 package com.hnepri.neo4j.common.util;
  2
  3 import java.lang.reflect.AccessibleObject;
  4 import java.lang.reflect.Array;
  5 import java.lang.reflect.Field;
  6 import java.lang.reflect.Modifier;
  7 import java.util.ArrayList;
  8 import java.util.Arrays;
  9 import java.util.HashMap;
 10 import java.util.Iterator;
 11 import java.util.List;
 12 import java.util.Map;
 13
 14 import org.apache.commons.lang3.StringUtils;
 15 import org.neo4j.graphdb.GraphDatabaseService;
 16 import org.neo4j.graphdb.RelationshipType;
 17 import org.neo4j.graphdb.Transaction;
 18
 19 import sun.reflect.ConstructorAccessor;
 20 import sun.reflect.FieldAccessor;
 21 import sun.reflect.ReflectionFactory;
 22
 23 /**
 24  * Description: 图数据库关系类型工具类。<br>
 25  * 1、可根据关系类型名称字符串动态生成关系类型枚举。<br>
 26  * 2、可管理动态生成的关系类型枚举列表。
 27  * Copyright: Copyright (c) 2015<br>
 28  * Company: 河南电力科学研究院智能电网所<br>
 29  * @author shangbingbing 2015-11-01编写
 30  * @version 1.0
 31  */
 32 public class GraphRelTypeUtil {
 33
 34     /**
 35* 构造函数。<br>
 36* 初始化对应的图数据库服务对象实例。
 37* @param graphDBService
 38*/
 39     public GraphRelTypeUtil(GraphDatabaseService graphDBService) {
 40   this.graphDBService = graphDBService;
 41     }
 42    
 43     private GraphDatabaseService graphDBService = null;
 44     /**
 45* 获取对应的图数据库服务对象实例。
 46* @return
 47*/
 48     public GraphDatabaseService getGraphDBService() {
 49   return this.graphDBService;
 50     }
 51    
 52     /**
 53* 构建事务。
 54* @return
 55*/
 56     public Transaction createTransaction() {
 57   return this.getGraphDBService().beginTx();
 58     }
 59    
 60     private GraphIndexUtil indexManager = null;
 61     /**
 62* 获取图数据库索引信息管理器。
 63* @return
 64*/
 65     public GraphIndexUtil getIndexManager() {
 66   if(this.indexManager == null) {
 67 this.indexManager = new GraphIndexUtil(this.getGraphDBService());
 68   }
 69   return this.indexManager;
 70     }
 71    
 72     private Map<String,RelationshipType> relationshipTypeList = null;
 73     /**
 74* 获取已动态生成的关系枚举类型列表。
 75* @return
 76*/
 77     @SuppressWarnings("deprecation")
 78     public Map<String, RelationshipType> getRelationshipTypeList() {
 79   if(this.relationshipTypeList == null) {
 80 this.relationshipTypeList = new HashMap<String, RelationshipType>();
 81
 82 Iterator<RelationshipType> iterator = this.getGraphDBService().getRelationshipTypes().iterator();
 83 while(iterator.hasNext()) {
 84     RelationshipType relType = iterator.next();
 85     String relTypeName = relType.name();
 86     this.relationshipTypeList.put(relTypeName, relType);
 87 }
 88   }
 89   return this.relationshipTypeList;
 90     }
 91
 92     /**
 93* 根据关系类型名称动态生成图数据库关系枚举类型。
 94* @param relTypeName
 95* @return
 96*/
 97     public RelationshipType create(String relTypeName) {
 98   if(StringUtils.isBlank(relTypeName)) {
 99 return null;
100   }
101   if(this.getRelationshipTypeList().containsKey(relTypeName) == false) {
102 addEnum(relTypeName);
103 RelationshipType relType = RelationshipTypeEnum.valueOf(relTypeName);
104 this.getRelationshipTypeList().put(relTypeName, relType);
105 return relType;
106   } else {
107 return this.getRelationshipTypeList().get(relTypeName);
108   }
109     }
110    
111     /**
112* 根据关系类型名称,获取对应的关系枚举类型。
113* @param relTypeName
114* @return
115*/
116     public RelationshipType get(String relTypeName) {
117   if(StringUtils.isBlank(relTypeName)) {
118 return null;
119   }
120   return this.create(relTypeName);
121     }
122    
123     /**
124* 根据关系类型名称,删除对应的关系枚举类型。
125* @param relTypeName
126* @return
127*/
128     public void remove(String relTypeName) {
129   if(this.getRelationshipTypeList().containsKey(relTypeName)) {
130 this.getRelationshipTypeList().remove(relTypeName);
131   }
132     }
133    
134     /**
135* 根据关系类型名称列表,初始化关系类型枚举列表。
136* @param relTypeNameList
137*/
138     public void init(List<String> relTypeNameList) {
139   if(relTypeNameList == null || relTypeNameList.size() == 0) {
140 return;
141   }
142   for(String relTypeName : relTypeNameList) {
143 create(relTypeName);
144   }
145     }
146    
147     private static ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
148    
149     private static void setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException,IllegalAccessException {
150   field.setAccessible(true);
151   Field modifiersField = Field.class.getDeclaredField("modifiers");
152   modifiersField.setAccessible(true);
153   int modifiers = modifiersField.getInt(field);
154  
155   modifiers &= ~Modifier.FINAL;
156   modifiersField.setInt(field, modifiers);
157  
158   FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
159   fa.set(target, value);
160     }
161    
162private static void blankField(Class<?> enumClass, String fieldName) throws NoSuchFieldException, IllegalAccessException {
163    for (Field field : Class.class.getDeclaredFields()) {
164  if (field.getName().contains(fieldName)) {
165AccessibleObject.setAccessible(new Field[] { field }, true);
166setFailsafeFieldValue(field, enumClass, null);
167break;
168  }
169   }
170     }
171
172     private static void cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException {
173   blankField(enumClass, "enumConstantDirectory");
174   blankField(enumClass, "enumConstants");
175     }
176
177     private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes) throws NoSuchMethodException {
178   Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
179   parameterTypes[0] = String.class;
180   parameterTypes[1] = int.class;
181   System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
182   return reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(parameterTypes));
183     }
184
185     private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues) throws Exception {
186   Object[] parms = new Object[additionalValues.length + 2];
187   parms[0] = value;
188   parms[1] = Integer.valueOf(ordinal);
189   System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
190   return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
191     }
192
193     @SuppressWarnings("unchecked")
194     private static <T extends Enum<?>> void addEnum(String enumName) {
195    if (!Enum.class.isAssignableFrom(RelationshipTypeEnum.class)) {
196  throw new RuntimeException("class " + RelationshipTypeEnum.class + " is not an instance of Enum");
197    }
198  
199    Field valuesField = null;
200    Field[] fields = RelationshipTypeEnum.class.getDeclaredFields();
201    for (Field field : fields) {
202  if (field.getName().contains("$VALUES")) {
203valuesField = field;
204break;
205  }
206    }
207    AccessibleObject.setAccessible(new Field[] { valuesField }, true);
208  
209    try {
210  
211  T[] previousValues = (T[]) valuesField.get(RelationshipTypeEnum.class);
212  List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
213  
214  T newValue = (T) makeEnum(RelationshipTypeEnum.class, enumName, values.size(), new Class<?>[] {}, new Object[] {});
215  values.add(newValue);
216  setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(RelationshipTypeEnum.class, 0)));
217  cleanEnumCache(RelationshipTypeEnum.class);
218    } catch (Exception e) {
219  e.printStackTrace();
220  throw new RuntimeException(e.getMessage(), e);
221    }
222     }
223 }
224
225 /**
226  * Description: 图数据库关系类型枚举。
227  * Copyright: Copyright (c) 2015<br>
228  * Company: 河南电力科学研究院智能电网所<br>
229  * @author shangbingbing 2015-11-01编写
230  * @version 1.0
231  */
232 enum RelationshipTypeEnum implements RelationshipType {
233     NONE
234 }

2.6 图数据库操作模板工具类(GraphTemplateUtil)

图数据库操作模板工具类,主要负责对以上工具集的组织和调用管理,以保证开发人员在调用习惯上遵循Neo4j嵌入式图数据库单例服务的原则。关键代码如下表所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
  1 package com.hnepri.neo4j.common.util;
  2
  3 import java.io.File;
  4 import java.util.HashMap;
  5 import java.util.Map;
  6
  7 import org.apache.commons.lang3.StringUtils;
  8 import org.neo4j.graphdb.GraphDatabaseService;
  9 import org.neo4j.graphdb.Transaction;
 10 import org.neo4j.graphdb.factory.GraphDatabaseFactory;
 11 import org.neo4j.io.fs.FileUtils;
 12
 13 import com.hnepri.neo4j.client.form.graph.bean.GraphConfigOption;
 14 import com.hnepri.neo4j.form.graph.util.GraphManageUtil;
 15
 16 /**
 17  * Description: Neo4j图数据库操作模板类。<br>
 18  * Copyright: Copyright (c) 2015<br>
 19  * Company: 河南电力科学研究院智能电网所<br>
 20  * @author shangbingbing 2015-11-01 编写
 21  * @version 1.0
 22  */
 23 public class GraphTemplate {
 24
 25     private String graphDBPath = "";
 26     /**
 27* 获取图数据库路径。
 28* @return
 29*/
 30     public String getGraphDBPath() {
 31   return graphDBPath;
 32     }
 33     private GraphDatabaseService graphDBService = null;
 34     /**
 35* 获取图数据库服务实例对象。
 36* @return
 37*/
 38     public GraphDatabaseService getGraphDBService() {
 39   if(StringUtils.isBlank(this.getGraphDBPath())) {
 40 try {
 41     throw new Exception("警告:没有配置图数据库路径信息!");
 42 } catch (Exception e) {
 43     e.printStackTrace();
 44 }
 45   }
 46   if(this.graphDBService == null) {
 47 this.graphDBService = new GraphDatabaseFactory().newEmbeddedDatabase(this.getGraphDBPath());
 48 registerShutdownHook();
 49   }
 50   return this.graphDBService;
 51     }
 52    
 53     /**
 54* 清除图数据库数据文件信息。
 55*/
 56     public void clearGraphDB() {
 57   try {
 58 FileUtils.deleteRecursively(new File(this.getGraphDBPath()));
 59 if(graphTemplateList.containsKey(this.getGraphDBPath())) {
 60     graphTemplateList.remove(this.getGraphDBPath());
 61 }
 62   } catch (Exception ex) {
 63 ex.printStackTrace();
 64   }
 65     }
 66    
 67     /**
 68* 注册图数据库关闭钩子。
 69*/
 70     public void registerShutdownHook() {
 71   Runtime.getRuntime().addShutdownHook(new Thread(){
 72 @Override
 73 public void run(){
 74     getGraphDBService().shutdown();
 75 }
 76   });
 77     }
 78    
 79     /**
 80* 构造函数。初始化图数据库路径。
 81* @param graphDBPath
 82*/
 83     private GraphTemplate(String graphDBPath) {
 84   this.graphDBPath = graphDBPath;
 85     }
 86    
 87     private static Map<String, GraphTemplate> graphTemplateList = new HashMap<String, GraphTemplate>();
 88     /**
 89* 根据图数据库路径信息,获取对应的GraphTemplate对象实例。<br>
 90* 目的是采用GraphTemplate的单例模式。
 91* @param graphPath
 92* @return
 93*/
 94     public static GraphTemplate getInstance(String graphPath) {
 95   if(graphTemplateList.containsKey(graphPath) == false) {
 96 GraphTemplate template = new GraphTemplate(graphPath);
 97 graphTemplateList.put(graphPath, template);
 98   }
 99   return graphTemplateList.get(graphPath);
100     }
101    
102     /**
103* 根据图数据库名称标示信息,获取对应的GraphTemplate对象实例。<br>
104* 目的是采用GraphTemplate的单例模式。
105* @param graphName
106* @return
107*/
108     public static GraphTemplate getInstanceByName(String graphName) {
109   if(GraphManageUtil.getGraphConfigOptionList().containsKey(graphName) == false) {
110 return null;
111   }
112   GraphConfigOption option = GraphManageUtil.getGraphConfigOptionList().get(graphName);
113   return getInstance(option.getPath());
114     }
115    
116     /**
117* 构建事务。
118* @return
119*/
120     public Transaction createTransaction() {
121   return this.getGraphDBService().beginTx();
122     }
123    
124     private GraphIndexUtil indexUtil = null;
125     /**
126* 获取图数据库索引信息管理功能库。
127* @return
128*/
129     public GraphIndexUtil getIndexUtil() {
130   if(this.indexUtil == null) {
131 this.indexUtil = new GraphIndexUtil(this.getGraphDBService());
132   }
133   return this.indexUtil;
134     }
135    
136     private GraphNodeUtil nodeUtil = null;
137     /**
138* 获取图数据库节点信息管理功能库。
139* @return
140*/
141     public GraphNodeUtil getNodeUtil() {
142   if(this.nodeUtil == null) {
143 this.nodeUtil = new GraphNodeUtil(this.getGraphDBService());
144   }
145   return this.nodeUtil;
146     }
147    
148     private GraphCypherUtil cypherUtil = null;
149     /**
150* 获取图数据库Cypher查询语言功能库。
151* @return
152*/
153     public GraphCypherUtil getCypherUtil() {
154   if(this.cypherUtil == null) {
155 this.cypherUtil = new GraphCypherUtil(this.getGraphDBService());
156   }
157   return this.cypherUtil;
158     }
159    
160     private GraphPathUtil pathUtil = null;
161     /**
162* 获取图数据库Path信息功能库。
163* @return
164*/
165     public GraphPathUtil getPathUtil() {
166   if(this.pathUtil == null) {
167 this.pathUtil = new GraphPathUtil(this.getGraphDBService());
168   }
169   return this.pathUtil;
170     }
171    
172     private GraphRelTypeUtil relTypeUtil = null;
173     /**
174* 获取图数据库关系类型信息功能库。
175* @return
176*/
177     public GraphRelTypeUtil getRelTypeUtil() {
178   if(this.relTypeUtil == null) {
179 this.relTypeUtil = new GraphRelTypeUtil(this.getGraphDBService());
180   }
181   return this.relTypeUtil;
182     }
183 }

2.7 数据分页模型GraphPageModel

数据分页模型主要是基于Cypher查询语言中的SKIP和LIMIT而设计的针对Node和Relationship的分页处理模型。关键代码如下表所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  1 package com.hnepri.neo4j.common.model;
  2
  3 import java.io.Serializable;
  4 import java.text.DecimalFormat;
  5 import java.util.ArrayList;
  6 import java.util.List;
  7
  8 import org.neo4j.graphdb.Node;
  9 import org.neo4j.graphdb.Relationship;
 10
 11 import com.hnepri.common.util.LogInfoUtil;
 12
 13 /**
 14  * Description: 图数据库数据分页模型类。<br>
 15  * 利用此类可分页管理Node数据和Relationship数据等。
 16  * Copyright: Copyright (c) 2015<br>
 17  * Company: 河南电力科学研究院智能电网所<br>
 18  * @author shangbingbing 2015-11-01编写
 19  * @version 1.0
 20  */
 21 public class GraphPageModel implements Serializable {
 22     private static final long serialVersionUID = 330410716100946538L;
 23     private int pageSize = 10;
 24     private int pageIndex = 1;
 25     private int prevPageIndex = 1;
 26     private int nextPageIndex = 1;
 27     private int pageCount = 0;
 28     private int pageFirstRowIndex = 1;
 29     private boolean hasNextPage = true;
 30     private int totalCount = 0;
 31     private long startTime = System.currentTimeMillis();
 32     private long endTime = System.currentTimeMillis();
 33     private List<Node> nodeList = new ArrayList<Node>();
 34     private List<Relationship> relationshipList = new ArrayList<Relationship>();
 35    
 36     /**
 37* 分页对象构造函数
 38* @param pageSize 每页记录数
 39*/
 40     public GraphPageModel(int pageSize) {
 41   this.pageSize = pageSize;
 42     }
 43
 44     /**
 45* 获取分页记录数量
 46* @return
 47*/
 48     public int getPageSize() {
 49   return pageSize;
 50     }
 51     /**
 52* 获取当前页序号
 53* @return
 54*/
 55     public int getPageIndex() {
 56   return pageIndex;
 57     }
 58     /**
 59* 设置当前页序号
 60* @param pageIndex
 61*/
 62     public void setPageIndex(int pageIndex) {
 63   if(pageIndex <= 0) {
 64 pageIndex = 1;
 65   }
 66   this.pageIndex = pageIndex;
 67     }
 68     /**
 69* 获取分页总数
 70* @return
 71*/
 72     public int getPageCount() {
 73   if(this.getTotalCount() == 0) {
 74 this.pageCount = 0;
 75   } else {
 76 int shang = this.getTotalCount() / this.getPageSize();
 77 int yu = this.getTotalCount() % this.getPageSize();
 78 if(yu > 0) {
 79     shang += 1;
 80 }
 81 this.pageCount = shang;
 82   }
 83   return pageCount;
 84     }
 85     /**
 86* 获取每页的第一行序号
 87* @return
 88*/
 89     public int getPageFirstRowIndex() {
 90   this.pageFirstRowIndex = (this.pageIndex - 1) * this.getPageSize() + 1;
 91   return pageFirstRowIndex;
 92     }
 93     /**
 94* 获取上一页序号
 95* @return
 96*/
 97     public int getPrevPageIndex() {
 98   if(this.pageIndex > 1) {
 99 this.prevPageIndex = this.pageIndex - 1;
100   } else {
101 this.prevPageIndex = 1;
102   }
103   return prevPageIndex;
104     }
105     /**
106* 获取下一页序号
107* @return
108*/
109     public int getNextPageIndex() {
110   if(this.pageIndex < this.pageCount) {
111 this.nextPageIndex = this.pageIndex + 1;   
112   } else {
113 this.nextPageIndex = this.pageCount;
114   }
115   return nextPageIndex;
116     }
117     /**
118* 跳转到下一页
119*/
120     public void nextPage() {
121   if(this.totalCount == 0 || this.getPageCount() == 0) {
122 this.pageIndex = 1;
123   } else {
124 if(this.pageIndex < this.pageCount) {
125     this.pageIndex = this.pageIndex + 1;   
126 } else {
127     this.pageIndex = this.pageCount;
128 }
129   }
130     }
131     /**
132* 跳转到上一页
133*/
134     public void prevPage() {
135   if(this.pageIndex > 1) {
136 this.pageIndex = this.pageIndex - 1;
137   } else {
138 this.pageIndex = 1;
139   }
140     }
141     /**
142* 获取是否有下一页
143* @return
144*/
145     public boolean isHasNextPage() {
146   if(this.pageIndex < this.getPageCount()) {
147 this.hasNextPage = true;
148   } else {
149 this.hasNextPage = false;
150   }
151   return hasNextPage;
152     }
153     /**
154* 获取总记录数   
155*/
156     public int getTotalCount() {
157   return totalCount;
158     }
159     /**
160* 获取总记录数   
161* @param totalCount
162*/
163     public void setTotalCount(int totalCount) {
164   this.totalCount = totalCount;
165     }
166     /**
167* 初始化起始时间(毫秒)
168*/
169     public void initStartTime() {
170   this.startTime = System.currentTimeMillis();
171     }
172     /**
173* 初始化截止时间(毫秒)
174*/
175     public void initEndTime() {
176   this.endTime = System.currentTimeMillis();
177     }
178     /**
179* 获取毫秒格式的耗时信息
180* @return
181*/
182     public String getTimeIntervalByMilli() {
183   return String.valueOf(this.endTime - this.startTime) + "毫秒";
184     }
185     /**
186* 获取秒格式的耗时信息
187* @return
188*/
189     public String getTimeIntervalBySecond() {
190   double interval = (this.endTime - this.startTime)/1000.0;
191   DecimalFormat df = new DecimalFormat("#.##");
192   return df.format(interval) + "秒";
193     }
194     /**
195* 打印时间信息
196*/
197     public void printTimeInfo() {
198   LogInfoUtil.printLog("起始时间:" + this.startTime);
199   LogInfoUtil.printLog("截止时间:" + this.endTime);
200   LogInfoUtil.printLog("耗费时间:" + this.getTimeIntervalBySecond());
201     }
202     /**
203* 获取Node检索结果列表
204* @return
205*/
206     public List<Node> getNodeList() {
207   return nodeList;
208     }
209     /**
210* 获取Relationship检索结果列表
211* @return
212*/
213     public List<Relationship> getRelationshipList() {
214   return relationshipList;
215     }
216 }

图形数据库 Neo4j(2) ----Java相关推荐

  1. java session使用_使用Neo4j和Java进行大数据分析 第2部分

    本文的第一部分介绍了Neo4j及其Cypher查询语言.如果您已经阅读了第1部分,那么您已经了解了为什么Neo4j和其他图形数据库特别受社交图形或网络中用户之间关系建模的影响.您还在开发环境中安装了N ...

  2. 图形数据库neo4j视频教程

    2019独角兽企业重金招聘Python工程师标准>>> Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中.它是一个嵌入式的.基于磁盘的.具备完全的 ...

  3. 图形数据库Neo4J简介

    最近我在用图形数据库来完成对一个初创项目的支持.在使用过程中觉得这种图形数据库实际上挺有意思的.因此在这里给大家做一个简单的介绍. NoSQL数据库相信大家都听说过.它们常常可以用来处理传统的关系型数 ...

  4. 利用Neo4j的Java驱动获取节点的所有直接关系

    利用Neo4j的Java驱动获取节点的所有直接关系 简单介绍 直接上代码 注解 总结 简单介绍 笔者利用Neo4j的Java驱动获取节点的所有直接关系,主要逻辑就是使用驱动查询Neo4j,遍历返回结果 ...

  5. NOSQL图形数据库 - Neo4j

    Neo4j入门指南 1.简介 2.特点 3.安装 4.基础操作 5.Java集成 5.1 内嵌数据库集成方式 5.2 服务器集成方式 5.3 Spring集成方式 参考文档 1.简介 Neo4j是一个 ...

  6. 使用Neo4j和Java进行大数据分析 第1部分

    几十年来,关系数据库一直主导着数据管理,但它们最近已经失去了NoSQL的替代品.虽然NoSQL数据存储不适合每个用例,但它们通常更适合大数据,这是处理大量数据的系统的简写.四种类型的数据存储用于大数据 ...

  7. 初识图形数据库--neo4j

    图形数据库是NoSQL数据库的一种类型,它应用图形理论存储实体之间的关系信息.图形数据库是一种非关系型数据库,它应用 图形理论存储实体之间的关系信息. Neo4J基于文件系统的一种图形数据库,用于处理 ...

  8. Windows下安装图形数据库Neo4j说明(4.3.2压缩包方式)

    文章目录 前言 一.下载安装包 二.安装Neo4j 1.安装包文件解压 2.配置系统环境变量 3. 启动Neo4j 4. 将Neo4j安装到Windows服务 5. Neo4j常用命令 三.安装常见问 ...

  9. 图形数据库Neo4j基本了解

    在深入学习图形数据库之前,首先理解属性图的基本概念.一个属性图是由顶点(Vertex),边(Edge),标签(Lable),关系类型和属性(Property)组成的有向图.顶点也称作节点(Node), ...

  10. php 可视化neo4j,开源图形数据库Neo4j使用 php开发

    先看看它的示例数据 打开 Neo4j Browser :play movie graph 写代码,然后点play执行 Cypher, the graph query language.Neo4j提供了 ...

最新文章

  1. SQLServer怎样把本地数据导入到远程服务器上(转载)
  2. 今晚8点直播 | 美团是怎么玩儿AI的?大牛揭秘美团超大规模数据集——美团大脑
  3. 程序员如果也能像C罗一样自律和勤奋,必将成为大神!
  4. struts2中result的type跳转类型总结
  5. ehchache验证缓存过期的api_Ehcache缓存时间设置
  6. 技术干货 | 轻松两步完成向 mPaaS 小程序传递启动参数
  7. codevs 1230【pb_ds】
  8. charles乱码_基于iOS的Charles抓包实践
  9. enumerate()使用
  10. zabbix之解决中文乱码
  11. 2020计算机二级取消vb,2020年计算机二级VB模拟试题及答案
  12. HDOJ1018 ( Big Number ) 【斯特林公式---处理阶乘及阶乘位数的问题】
  13. 《thor过滤器 thor过滤规则合集资源》500+
  14. win10系统个人服务器配置,个人电脑win10配置服务器吗
  15. 忘了是出自雪中还是剑来或者就是癞蛤蟆?反正应该是烽火大太监的句子吧。还掺杂了许多别家的,记不清谁写的了,或许有西藏的佛陀
  16. 【100个 Unity实用技能】 | Unity 在代码中 动态改变RectTransform位置及宽高 的方法整理
  17. 用python123.io编程世界你好_python语言IO编程
  18. [DIV/CSS] 用CSS和JS打造一个简单的图片编辑器
  19. 检测IE浏览器类型并跳转至谷歌浏览器打开网页
  20. 文章分类标签数据库设计

热门文章

  1. java spring log4j_配置spring的log4j日志记录
  2. android snackbar 底部,Android KitKat:Snackbar不在屏幕的底部
  3. 如何用python写程序设置当前打印机为默认打印机,从Python打印到标准打印机?
  4. 手机蓝牙如何减少延时_JEET ONE真无线耳机测评-高颜值低延时的集合
  5. CentOS部署OpenStack过程-网络服务
  6. react-native-router-flux 页面跳转与传值
  7. 大数据-05-Spark之读写HBase数据
  8. python接口自动化4-绕过验证码登录(cookie) (转载)
  9. 第 200 章 Office
  10. Objective-C写出Json文件(可作配置文件)