图形数据库 Neo4j(2) ----Java
Neo4j 是一个高性能的 NoSQL 图形数据库。Neo4j 使用图(graph)相关的概念来描述数据模型,把数据保存为图中的节点以及节点之间的关系。很多应用中数据之间的关系,可以很直接地使用图中节点和关系的概念来建模。对于这样的应用,使用 Neo4j 来存储数据会非常的自然,要优于使用关系数据库。本文对 Neo4j 进行了深入的介绍,并结合具体的实例来进行详细的说明,可以让您对 Neo4j 有深入的了解,从而可以在应用开发中恰当地选择 Neo4j 来作为存储方式。
2 评论
2013 年 6 月 20 日
- 内容
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 应用开发概述
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 }
|
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 }
|
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 }
|
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相关推荐
- java session使用_使用Neo4j和Java进行大数据分析 第2部分
本文的第一部分介绍了Neo4j及其Cypher查询语言.如果您已经阅读了第1部分,那么您已经了解了为什么Neo4j和其他图形数据库特别受社交图形或网络中用户之间关系建模的影响.您还在开发环境中安装了N ...
- 图形数据库neo4j视频教程
2019独角兽企业重金招聘Python工程师标准>>> Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中.它是一个嵌入式的.基于磁盘的.具备完全的 ...
- 图形数据库Neo4J简介
最近我在用图形数据库来完成对一个初创项目的支持.在使用过程中觉得这种图形数据库实际上挺有意思的.因此在这里给大家做一个简单的介绍. NoSQL数据库相信大家都听说过.它们常常可以用来处理传统的关系型数 ...
- 利用Neo4j的Java驱动获取节点的所有直接关系
利用Neo4j的Java驱动获取节点的所有直接关系 简单介绍 直接上代码 注解 总结 简单介绍 笔者利用Neo4j的Java驱动获取节点的所有直接关系,主要逻辑就是使用驱动查询Neo4j,遍历返回结果 ...
- NOSQL图形数据库 - Neo4j
Neo4j入门指南 1.简介 2.特点 3.安装 4.基础操作 5.Java集成 5.1 内嵌数据库集成方式 5.2 服务器集成方式 5.3 Spring集成方式 参考文档 1.简介 Neo4j是一个 ...
- 使用Neo4j和Java进行大数据分析 第1部分
几十年来,关系数据库一直主导着数据管理,但它们最近已经失去了NoSQL的替代品.虽然NoSQL数据存储不适合每个用例,但它们通常更适合大数据,这是处理大量数据的系统的简写.四种类型的数据存储用于大数据 ...
- 初识图形数据库--neo4j
图形数据库是NoSQL数据库的一种类型,它应用图形理论存储实体之间的关系信息.图形数据库是一种非关系型数据库,它应用 图形理论存储实体之间的关系信息. Neo4J基于文件系统的一种图形数据库,用于处理 ...
- Windows下安装图形数据库Neo4j说明(4.3.2压缩包方式)
文章目录 前言 一.下载安装包 二.安装Neo4j 1.安装包文件解压 2.配置系统环境变量 3. 启动Neo4j 4. 将Neo4j安装到Windows服务 5. Neo4j常用命令 三.安装常见问 ...
- 图形数据库Neo4j基本了解
在深入学习图形数据库之前,首先理解属性图的基本概念.一个属性图是由顶点(Vertex),边(Edge),标签(Lable),关系类型和属性(Property)组成的有向图.顶点也称作节点(Node), ...
- php 可视化neo4j,开源图形数据库Neo4j使用 php开发
先看看它的示例数据 打开 Neo4j Browser :play movie graph 写代码,然后点play执行 Cypher, the graph query language.Neo4j提供了 ...
最新文章
- SQLServer怎样把本地数据导入到远程服务器上(转载)
- 今晚8点直播 | 美团是怎么玩儿AI的?大牛揭秘美团超大规模数据集——美团大脑
- 程序员如果也能像C罗一样自律和勤奋,必将成为大神!
- struts2中result的type跳转类型总结
- ehchache验证缓存过期的api_Ehcache缓存时间设置
- 技术干货 | 轻松两步完成向 mPaaS 小程序传递启动参数
- codevs 1230【pb_ds】
- charles乱码_基于iOS的Charles抓包实践
- enumerate()使用
- zabbix之解决中文乱码
- 2020计算机二级取消vb,2020年计算机二级VB模拟试题及答案
- HDOJ1018 ( Big Number ) 【斯特林公式---处理阶乘及阶乘位数的问题】
- 《thor过滤器 thor过滤规则合集资源》500+
- win10系统个人服务器配置,个人电脑win10配置服务器吗
- 忘了是出自雪中还是剑来或者就是癞蛤蟆?反正应该是烽火大太监的句子吧。还掺杂了许多别家的,记不清谁写的了,或许有西藏的佛陀
- 【100个 Unity实用技能】 | Unity 在代码中 动态改变RectTransform位置及宽高 的方法整理
- 用python123.io编程世界你好_python语言IO编程
- [DIV/CSS] 用CSS和JS打造一个简单的图片编辑器
- 检测IE浏览器类型并跳转至谷歌浏览器打开网页
- 文章分类标签数据库设计
热门文章
- java spring log4j_配置spring的log4j日志记录
- android snackbar 底部,Android KitKat:Snackbar不在屏幕的底部
- 如何用python写程序设置当前打印机为默认打印机,从Python打印到标准打印机?
- 手机蓝牙如何减少延时_JEET ONE真无线耳机测评-高颜值低延时的集合
- CentOS部署OpenStack过程-网络服务
- react-native-router-flux 页面跳转与传值
- 大数据-05-Spark之读写HBase数据
- python接口自动化4-绕过验证码登录(cookie) (转载)
- 第 200 章 Office
- Objective-C写出Json文件(可作配置文件)