第 19 章 在 Python 中使用 Neo4j 嵌入模式
这描述了 _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 |
或者手工安装:
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。也要注意你下载的地址由你使用的版本决定。 |
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 查询
你能在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. 遍历查询结果
一个遍历查询能给你三个不同的结果之一:nodes, relationships或者 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安装程序。这也包括类似备份数据库和监控服务器健康状况等话题的介绍。