第 19 章 在 Python 中使用 Neo4j 嵌入模式

优质
小牛编辑
132浏览
2023-12-01

这描述了 _neo4j-embedded_,让你在Python应用中嵌入Neo4j数据库的一个Python库。

从参考文档和这个章节的安装介绍分开,你可以参考:第 9 章 在Python应用中使用Neo4j。

这个工程在GitHub上面的源代码地址: https://github.com/neo4j/python-embedded

19.1. 安装

注意:Neo4j 数据库(来自社区版)本身就被包括在 Neo4j 嵌入模式发行版中。 The Neo4j database itself (from the Community Edition) is included in the neo4j-embedded distribution.

19.1.1. 安装到OSX/Linux

先决条件

特别注意:确保使用的整个堆栈要么是64位的,要么是32位(默认就是32位)。这都是为了正常使用JVM,Python和JPype。

首先,安装JPype:

1.从http://sourceforge.net/projects/jpype/files/JPype/下载JPype的最新版本。

2.解压下载压缩包。

3.打开控制台,并进入压缩包目录。

4.运行命令:`sudo python setup.py install`。

JPype在Debian的源里面也有:

1

sudoapt-get installpython-jpype

然后,确保 +JAVA_HOME+环境参数设置到了你的jre和 'jdk'目录,保证JPype呢给你找到JVM。

注意: 在OSX上面安装是有问题的。看下面在Stack Overflow的讨论来获取更多的帮助:http://stackoverflow.com/questions/8525193/cannot-install-jpype-on-os-x-lion-to-use-with-neo4j 。

安装 neo4j-embedded

你可以用你的Python包管理工具安装neo4j-embedded:

1

sudopip installneo4j-embedded

1

sudoeasy_install neo4j-embedded

或者手工安装:

1.从 32位, 64位下载JPype最新版本。

2.解压下载的压缩文件。

3.打开控制台并进入到解压目录。

4.运行命令: sudo python setup.py install

19.1.2. 安装到 Windows

先决条件

警告: 确保使用的整个堆栈要么是64位的,要么是32位(默认就是32位)。这都是为了正常使用JVM,Python,JPype和所有额外的DLL。

首先,安装JPype:

注意

注意JPype只工作在Python 2.6 和 2.7。也要注意你下载的地址由你使用的版本决定。

1.从 32位, 64位下载JPype最新版本。

2.运行安装程序。

然后,确保 +JAVA_HOME+环境参数设置到了你的jre和 'jdk'目录。要了解详细的环境参数设置情况,请参考:“解决缺失DLL 文件的问题”一节

注意:如果有JPype需要的DLL文件缺失,请参考: “解决缺失DLL 文件的问题”一节的介绍来修复它。

安装 neo4j-embedded

1.从http://pypi.python.org/pypi/neo4j-embedded/下载最新版本。

2.运行安装程序。

解决缺失DLL 文件的问题

Windows的某些版本缺失了需要编程启动一个JVM的DLL文件。你需要保证 +IEShims.dll+和某些调试用的DLL文件在Windows上面。

IEShims.dll一般是在 Internet Explorer 的安装包里面。要让这个文件在你的系统路径下面,你需要增加 IE 的安装目录到你的 +PATH+。

1.右键点击 "我的电脑" 或者 "电脑"。

2.选择 "属性"。

3.点击 "高级" 或者 "高级系统设置"。

4.点击 "环境变量" 按钮。

5.找到path变量,并增加路径 C:\Program Files\Internet Explorer到他的后面(如果你的IE在其他目录,请用其他目录代替)。

调试涉及到的DLL文件都在Microsoft Visual C++ Redistributable库里面。

- 32bit Windows: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=5555

- 64bit Windows: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=14632

如果你依然还有问题,你可以使用 http://www.dependencywalker.com/打开你的 jvm.dll(在 JAVA_HOME/bin/client/或者 JAVA_HOME/bin/server/下面),然后他会告诉你是否缺失文件。

19.2. Core API

这个章节描述了如何建立环境到运行和如何做一些基本的操作。

19.2.1. 开始

创建一个数据库

1

2

3

4

5

6

7

fromneo4j importGraphDatabase

# Create db

db =GraphDatabase(folder_to_put_db_in)

# Always shut down your database

db.shutdown()

用配置创建一个数据库

要了解你能使用的配置选项,请参考:第 21 章 配置和调优。

1

2

3

4

5

6

fromneo4j importGraphDatabase

# Example configuration parameters

db =GraphDatabase(folder_to_put_db_in, string_block_size=200, array_block_size=240)

db.shutdown()

JPype JVM 配置

你能使用 NEO4J_PYTHON_JVMARGS环境变量来设置扩展参数,以便传递给 JVM。这是可以使用的,比如增加数据库的最大内存。

注意你必须在引入neo4j包之前设置这个,要么在你启动python之前设置,要么在你的应用中通过程序来设置。

1

2

3

importos

os.environ['NEO4J_PYTHON_JVMARGS'] ='-Xms128M -Xmx512M'

importneo4j

你可以通过使用环境变量 NEO4J_PYTHON_CLASSPATH来重载neo4j-embedded要使用的类路径。

19.2.2. 事务

所以的写到数据库的操作都必须在一个事务中执行。这确保了你的数据库不会处于一个数据不一致的状态。

了解关于如何在Neo4j中控制事务的细节,请参考:第 12 章 事务管理。

我们用python 的 with声明来定义一个事务。如果你使用一个Python的老版本,你可呢给你必须引入 with声明。

1

from__future__ importwith_statement

无论哪种方式,这就是进入事务的方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# Start a transaction

with db.transaction:

# This is inside the transactional

# context. All work done here

# will either entirely succeed,

# or no changes will be applied at all.

# Create a node

node =db.node()

# Give it a name

node['name'] ='Cat Stevens'

# The transaction is automatically

# commited when you exit the with

# block.

19.2.3. 节点

这个地方描述了节点对象的一些特殊操作。要了解关于如何控制节点和关系的属性的文档,请参考:第 19.2.5 节 “属性”

创建一个节点

1

2

3

with db.transaction:

# Create a node

thomas =db.node(name='Thomas Anderson', age=42)

通过Id找到一个节点

1

2

3

4

5

6

7

# You don't have to be in a transaction

# to do read operations.

a_node =db.node[some_node_id]

# Ids on nodes and relationships are available via the "id"

# property, eg.:

node_id =a_node.id

找到参考节点

1

reference =db.reference_node

移除一个节点

1

2

3

with db.transaction:

node =db.node()

node.delete()

提示

也可以参考:第 12.5 节 “Delete semantics”。

通过id移除一个节点

1

2

with db.transaction:

deldb.node[some_node_id]

从一个节点上访问他的关系

要获取关于你在关系对象上面你能做什么操作的细节,请参考: 第 19.2.4 节 “关系”

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

# All relationships on a node

forrel ina_node.relationships:

pass

# Incoming relationships

forrel ina_node.relationships.incoming:

pass

# Outgoing relationships

forrel ina_node.relationships.outgoing:

pass

# Relationships of a specific type

forrel ina_node.mayor_of:

pass

# Incoming relationships of a specific type

forrel ina_node.mayor_of.incoming:

pass

# Outgoing relationships of a specific type

forrel ina_node.mayor_of.outgoing:

pass

获取并计算节点个数

使用这个必须小心,在大型数据集中它会变得非常慢。

1

2

3

4

5

6

fornode indb.nodes:

pass

# Shorthand for iterating through

# and counting all nodes

number_of_nodes =len(db.nodes)

19.2.4. 关系

这节描述了关系对象的一些操作。要获取关于如何控制节点和关系上面的属性,请参考: 第 19.2.5 节 “属性”

创建一个关系

1

2

3

4

5

6

7

8

9

10

11

12

with db.transaction:

# Nodes to create a relationship between

steven =self.graphdb.node(name='Steve Brook')

poplar_bluff =self.graphdb.node(name='Poplar Bluff')

# Create a relationship of type "mayor_of"

relationship =steven.mayor_of(poplar_bluff, since="12th of July 2012")

# Or, to create relationship types with names

# that would not be possible with the above

# method.

steven.relationships.create('mayor_of', poplar_bluff, since="12th of July 2012")

通过Id找到一个关系

1

the_relationship =db.relationship[a_relationship_id]

移除一个关系

1

2

3

4

5

6

7

8

with db.transaction:

# Create a relationship

source =db.node()

target =db.node()

rel =source.Knows(target)

# Delete it

rel.delete()

提示

也可以参考:第 12.5 节 “Delete semantics”。

通过Id移除一个关系

1

2

with db.transaction:

deldb.relationship[some_relationship_id]

关系的起点,终点和关系类型

1

2

3

4

relationship_type =relationship.type

start_node =relationship.start

end_node =relationship.end

获取所有的关系以及数量

Use this with care, it will become extremely slow in large datasets.

1

2

3

4

5

6

forrel indb.relationships:

pass

# Shorthand for iterating through

# and counting all relationships

number_of_rels =len(db.relationships)

19.2.5. 属性

节点和关系都可以有属性,所以这个部分介绍的内容同时适合节点和关系。属性允许的值包括:字符串,数字,布尔型以及数组。在咩一个数组内部,所有的值的类型都是相同的。

设置属性值

1

2

3

4

5

with db.transaction:

node_or_rel['name'] ='Thomas Anderson'

node_or_rel['age'] =42

node_or_rel['favourite_numbers'] =[1,2,3]

node_or_rel['favourite_words'] =['banana','blue']

获取属性值

1

numbers =node_or_rel['favourite_numbers']

移除属性

1

2

with db.transaction:

delnode_or_rel['favourite_numbers']

通过属性轮询

1

2

3

4

5

6

7

8

9

10

11

# Loop key and value at the same time

forkey, value innode_or_rel.items():

pass

# Loop property keys

forkey innode_or_rel.keys():

pass

# Loop property values

forvalue innode_or_rel.values():

pass

19.2.6. 路径

一个路径对象表示在一个图中的两个节点之间的一个路径。路径因此至少包括两个节点和一个关系,但不限长度。这个对象在API的不同部分使用,最多的地方请参考: 遍历查询。

访问开始节点和结束节点

1

2

start_node =path.start

end_node =path.end

访问关系

1

last_relationship =path.last_relationship

通过完整的路径轮询

你能直接通过一个路径的所有元素进行轮询,或者你能通过节点或者关系来选择一个进行轮询。当你通过所有元素进行轮询时,第一个元素将成为开始节点,第二个会成为第一个关系,第三个节点是关系要到达的地方。

1

2

3

4

5

6

7

8

9

10

11

12

foritem inpath:

# Item is either a Relationship,

# or a Node

pass

fornodes inpath.nodes:

# All nodes in a path

pass

fornodes inpath.relationships:

# All relationships in a path

pass

19.3. 索引

为了快速通过属性找到节点或者关系,Neo4j支持索引。这个常被用来找到 traversals用的起始节点。

默认情况下,相关的索引是由Apache Lucene提供的。但也能使用其他索引实现来提供。

您可以创建任意数量的命名索引。每个索引控制节点或者关系,而每个索引都通过key/value/object三个参数来工作。其中object要么是一个节点,要么是一个关系,取决于索引类型。

19.3.1. 索引管理

就像 REST API一样,所有到一个索引的写操作都必须在一个事务中完成。

创建一个索引

创建一个带配置的索引

1

2

3

4

5

6

7

8

with db.transaction:

# Create a relationship index

rel_idx =db.relationship.indexes.create('my_rels')

# Create a node index, passing optional

# arguments to the index provider.

# In this case, enable full-text indexing.

node_idx =db.node.indexes.create('my_nodes', type='fulltext')

接收一个预先存在的索引

1

2

3

4

with db.transaction:

node_idx =db.node.indexes.get('my_nodes')

rel_idx =db.relationship.indexes.get('my_rels')

移除索引

1

2

3

4

5

6

with db.transaction:

node_idx =db.node.indexes.get('my_nodes')

node_idx.delete()

rel_idx =db.relationship.indexes.get('my_rels')

rel_idx.delete()

检查一个索引是否存在

1

exists =db.node.indexes.exists('my_nodes')

19.3.2. 被索引的东西

增加节点或者关系到索引

1

2

3

4

5

6

7

8

9

10

11

12

13

14

with db.transaction:

# Indexing nodes

a_node =db.node()

node_idx =db.node.indexes.create('my_nodes')

# Add the node to the index

node_idx['akey']['avalue'] =a_node

# Indexing relationships

a_relationship =a_node.knows(db.node())

rel_idx =db.relationship.indexes.create('my_rels')

# Add the relationship to the index

rel_idx['akey']['avalue'] =a_relationship

移除索引的条目

在不同的层面移除索引的条目。看下面的范例了解信息。

1

2

3

4

5

6

7

8

9

# Remove specific key/value/item triplet

delidx['akey']['avalue'][item]

# Remove all instances under a certain

# key

delidx['akey'][item]

# Remove all instances all together

delidx[item]

19.3.3. 查询一个索引

你可以通过两种方式接收索引的条目。要么你做一个直接的查找,要么你执行一个查询。直接的查找在不同的索引服务是一样的,索引的语法取决于你使用的索引服务。像之前提过的一样,Lucene是默认索引服务并且它也是使用最多的索引服务。了解你想使用的Lucene,请参考: Lucene query language

一个编程生成Lucene查询的python库在这里:GitHub

重要

除非遍历整个索引结果,否则当你用完它后你必须关闭结果。如果你没有关闭,数据库不知道他什么时候能释放结果资源。

直接轮循

1

2

3

4

5

6

7

hits =idx['akey']['avalue']

foritem inhits:

pass

# Always close index results when you are

# done, to free up resources.

hits.close()

查询

1

2

3

4

5

6

7

hits =idx.query('akey:avalue')

foritem inhits:

pass

# Always close index results when you are

# done, to free up resources.

hits.close()

19.4. Cypher 查询

19.4.1. 查询并读取结果

19.4.2. 参数化,并准备查询

你能在neo4j-embedded中使用Cypher查询语言。要阅读更多关于cypher的语法以及你能使用的非常方便的工具,请参考:第 15 章 Cypher查询语言。

19.4.1. 查询并读取结果

基本查询

执行一个文本查询,如下:

1

result =db.query("START n=node(0) RETURN n")

接收查询的结果

Cypher返回一个表格式的结果。你要么通过表格的一行一行的轮循,要么你给定列取里面的值。

这是如何进行一行一行的轮循的:

1

2

3

4

5

6

7

8

9

root_node ="START n=node(0) RETURN n"

# Iterate through all result rows

forrow indb.query(root_node):

node =row['n']

# We know it's a single result,

# so we could have done this as well

node =db.query(root_node).single['n']

这儿是给定列取值:

1

2

3

4

5

6

7

8

9

10

11

root_node ="START n=node(0) RETURN n"

# Fetch an iterator for the "n" column

column =db.query(root_node)['n']

forcell incolumn:

node =cell

# Coumns support "single":

column =db.query(root_node)['n']

node =column.single

列出结果中的列

你能得到列名的一个列表,如下:

1

2

3

4

result =db.query("START n=node(0) RETURN n,count(n)")

# Get a list of the column names

columns =result.keys()

19.4.2. 参数化,并准备查询

参数化查询

Cypher支持参数化查询,请参考:cypher-parameters。这里是你在neo4j-embedded如何使用他们。

1

2

3

result =db.query("START n=node({id}) RETURN n",id=0)

node =result.single['n']

准备查询

准备查询,就是你可以对一个cypher查询进行预解析,这个功能已经被废弃了。Cypher能识别之前已经解析过的查询而不会解析相同的字符串两次。

因此,如果你们使用两次以上,实际上所有的cypher查询都是预解析查询的。使用参数化的查询得到完全的功能 - 然后一个通用的查询将被解析,以后每次执行的时候都会用参数修改它。

19.5. 遍历查询

警告

在neo4j-embedded for python中支持的遍历查询在Neo4j 1.7 GA已经废弃了。请参考 第 19.4 节 “Cypher 查询”或者用核心API代替。因为遍历查询框架要求在JVM和python直接有一个紧密的耦合,所以为了提升性能,我们需要打破这个耦合。

下面的文档在neo4j-embedded 1.8中被移除了,而对遍历的支持在neo4j-embedded 1.9已经被删除了。

在这使用的遍历查询API本质上和Java API中的是一样的,略有一点修改。

遍历查询开始于一个给定的节点而使用大量的规则在图中移动以便找到我们想要的部分。

19.5.1. 基本的遍历查询

跟随一个关系

最基本的遍历查询简单的跟随某一个关系类型,而返回他们遇到的一切。默认情况下,莫个节点都只会被访问一次所以他们没有死循环的风险。

1

2

3

4

5

6

7

8

traverser =db.traversal()\

.relationships('related_to')\

.traverse(start_node)

# The graph is traversed as

# you loop through the result.

fornode intraverser.nodes:

pass

在一个特定的方向跟随一个关系

你可以告诉遍历查询你只跟随某一方向的关系

1

2

3

4

5

fromneo4j importOUTGOING, INCOMING, ANY

traverser =db.traversal()\

.relationships('related_to', OUTGOING)\

.traverse(start_node)

跟随多个关系类型

你能指定无数的关系类型以及方向来跟随。

1

2

3

4

5

6

fromneo4j importOUTGOING, INCOMING, ANY

traverser =db.traversal()\

.relationships('related_to', INCOMING)\

.relationships('likes')\

.traverse(start_node)

19.5.2. 遍历查询结果

一个遍历查询能给你三个不同的结果之一:nodesrelationships或者 paths

遍历查询是懒加载执行的,意味着只有当你轮循结果集时才会真正的去遍历。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

traverser =db.traversal()\

.relationships('related_to')\

.traverse(start_node)

# Get each possible path

forpath intraverser:

pass

# Get each node

fornode intraverser.nodes:

pass

# Get each relationship

forrelationship intraverser.relationships:

pass

19.5.3. 唯一性

为了避免无限死循环,定义在遍历中可以重新访问的部分是非常重要的。默认情况下,唯一性参数设置为: +NODE_GLOBAL+,这意味着每个节点只能被访问一次。

这儿有一些其他设置可以使用。

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

fromneo4j importUniqueness

# Available options are:

Uniqueness.NONE

# Any position in the graph may be revisited.

Uniqueness.NODE_GLOBAL

# Default option

# No node in the entire graph may be visited

# more than once. This could potentially

# consume a lot of memory since it requires

# keeping an in-memory data structure

# remembering all the visited nodes.

Uniqueness.RELATIONSHIP_GLOBAL

# No relationship in the entire graph may be

# visited more than once. For the same

# reasons as NODE_GLOBAL uniqueness, this

# could use up a lot of memory. But since

# graphs typically have a larger number of

# relationships than nodes, the memory

# overhead of this uniqueness level could

# grow even quicker.

Uniqueness.NODE_PATH

# A node may not occur previously in the

# path reaching up to it.

Uniqueness.RELATIONSHIP_PATH

# A relationship may not occur previously in

# the path reaching up to it.

Uniqueness.NODE_RECENT

# Similar to NODE_GLOBAL uniqueness in that

# there is a global collection of visited

# nodes each position is checked against.

# This uniqueness level does however have a

# cap on how much memory it may consume in

# the form of a collection that only

# contains the most recently visited nodes.

# The size of this collection can be

# specified by providing a number as the

# second argument to the

# uniqueness()-method along with the

# uniqueness level.

Uniqueness.RELATIONSHIP_RECENT

# works like NODE_RECENT uniqueness, but

# with relationships instead of nodes.

traverser =db.traversal()\

.uniqueness(Uniqueness.NODE_PATH)\

.traverse(start_node)

19.5.4. 顺序

你能通过宽度优先或者深度优先遍历。深度优先是默认使用的,因为他消耗更少的内存。

1

2

3

4

5

6

7

8

9

10

# Depth first traversal, this

# is the default.

traverser =db.traversal()\

.depthFirst()\

.traverse(self.source)

# Breadth first traversal

traverser =db.traversal()\

.breadthFirst()\

.traverse(start_node)

19.5.5. 评估器 - 高级过滤器

为了能在其他条件下遍历,比如节点属性,或者更复杂的比如邻居节点或者模式,我们都需要使用评估器。一个评估器是一个将一个路径作为参数的普通Python方法,返回下一个将要做的事情的描述。

路径参数是遍历器当前的位置,而我们下一步能做的事情是下面四种之一,就像下面的范例一样。

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

fromneo4j importEvaluation

# Evaluation contains the four

# options that an evaluator can

# return. They are:

Evaluation.INCLUDE_AND_CONTINUE

# Include this node in the result and

# continue the traversal

Evaluation.INCLUDE_AND_PRUNE

# Include this node in the result, but don't

# continue the traversal

Evaluation.EXCLUDE_AND_CONTINUE

# Exclude this node from the result, but

# continue the traversal

Evaluation.EXCLUDE_AND_PRUNE

# Exclude this node from the result and

# don't continue the traversal

# An evaluator

defmy_evaluator(path):

# Filter on end node property

ifpath.end['message'] =='world':

returnEvaluation.INCLUDE_AND_CONTINUE

# Filter on last relationship type

ifpath.last_relationship.type.name() =='related_to':

returnEvaluation.INCLUDE_AND_PRUNE

# You can do even more complex things here, like subtraversals.

returnEvaluation.EXCLUDE_AND_CONTINUE

# Use the evaluator

traverser =db.traversal()\

.evaluator(my_evaluator)\

.traverse(start_node)

部分 IV. 操作

这个部分将介绍如何安装和维护一个Neo4j安装程序。这也包括类似备份数据库和监控服务器健康状况等话题的介绍。