daihongshu 2014-12-01
NOSQL的图数据库之Neo4j的学习:
neo4j官方表示性能良好,支持上亿级别数量的节点,但是成熟的资料不多,以下是在项目中的简单应用。
1、在安装的过程中对jdk有一定的要求,我所安装的neo4j版本就要求jdk是1.7的。
2、对于neo4j的使用有两种方式:一种是嵌入式方式,另一种是提供了Rest访问的
服务器模式。
一、Rest方式操作Neo4j:
这里应用了Jersey,通过Jersey客户端API调用rest风格的Web服务,这样就不用
关闭neo4j的服务,进行相关数据的增删改查了。
finalStringSERVER_ROOT_URI="http://10.0.11.144:7474/db/data/";
1、查询
String cypherUri = SERVER_ROOT_URI + "cypher"; String queryStr = "start n = node(*) return n.source_id"; //查询所有的source_id属性。 JSONObject jsObject = new JSONObject(); jsObject.put("query", queryStr); WebResource resource = Client.create().resource( cypherUri ); ClientResponse response = resource.accept( MediaType.APPLICATION_JSON_TYPE ) .type( MediaType.APPLICATION_JSON_TYPE ) .entity( jsObject.toString() ).post( ClientResponse.class ); String returnStr=String.format( "POST [%s] to [%s], status code [%d], RETURNED DATA: "+ System.getProperty( "line.separator" ) + "%s", queryStr, cypherUri, response.getStatus(), response.getEntity( String.class ) ) ; String resultData=returnStr.split("RETURNED DATA:")[1]; JSONObject resultObj = JSONObject.fromObject(resultData); String resultStr = resultObj.getString("data"); if(null != resultStr && !"".equals(resultStr)){ resultStr = resultStr.replace("[", "").replace("]", "").replace("\"", ""); }else{ resultStr = ""; } response.close(); return resultStr;
2、删除
neo4j的删除,必须先删除其relationship,再删除其节点,否则删除失败!
这里根据source_id=123进行删除节点,同时也可以进行批量删除:
wheren.source_id=123ORn.source_id=124ORn.source_id=156。
但是应注意:因为是rest方式进行删除,这些参数不能拼接的太长,否则会报错.一般控制在几十个左右。
String cypherUri = SERVER_ROOT_URI + "cypher"; String queryStr = "START n=node(*) where n.source_id= 123 match n-[r]-() delete n,r;"; JSONObject jsObject = new JSONObject(); jsObject.put("query", queryStr); WebResource resource = Client.create().resource( cypherUri ); ClientResponse response = resource.accept( MediaType.APPLICATION_JSON_TYPE ) .type( MediaType.APPLICATION_JSON_TYPE ) .entity( jsObject.toString() ).post( ClientResponse.class ); String returnStr=String.format( "POST [%s] to [%s], status code [%d], RETURNED DATA: "+ System.getProperty( "line.separator" ) + "%s", queryStr, cypherUri, response.getStatus(), response.getEntity( String.class ) ) ; response.close();
3、添加:
注意:在采用rest这种增量式的添加的时候,由于新增的数据与已经存在的一致,但是再次添加的时候,
neo4j仍然能够正常的添加进去,以为neo4j是根据自己维护节点的id的,所以在增加新的数据之前,很有必要
对数据是否已在neo4j中创建进行判断.
private URI createNode() { String nodeEntryPointUri = SERVER_ROOT_URI + "node"; WebResource resource = Client.create().resource(nodeEntryPointUri); ClientResponse response = resource.accept(MediaType.APPLICATION_JSON) .type(MediaType.APPLICATION_JSON).entity("{}").post(ClientResponse.class); URI location = response.getLocation(); response.close(); return location; }
//这是创建的新节点,返回的URI是什么呢?我们看一下获取已经存在的节点的URI
private URI getNodesURI(String exits) { //exits是neo4j中的节点的id. String nodeEntryPointUri = SERVER_ROOT_URI + "node/" + exits.replace("\"", ""); URI location = URI.create(nodeEntryPointUri); return location; }
1、利用上面创建新节点的方法创建两个节点.
URInodes=createNode();
URInodeo=createNode();
2、为这两个节点增加source_id属性.
addProperty(nodes,"source_id",99);
addProperty(nodeo,"source_id",100);
方法addProperty的具体实现如下:
private void addProperty(URI nodeUri, String propertyName, int propertyValue) { String propertyUri = nodeUri.toString() + "/properties/" + propertyName; WebResource resource = Client.create().resource(propertyUri); ClientResponse response = resource.accept(MediaType.APPLICATION_JSON) .type(MediaType.APPLICATION_JSON) .entity("\"" + propertyValue + "\"").put(ClientResponse.class); response.close(); }
3、创建好了两个节点并为其增加了属性,那么如何为他们添加关联关系呢.
/** * 增加neo4j的两个节点之间的关系. * @param startNode 起始节点的URI * @param endNode 指向节点的URI * @param relationshipType 关系类型:一般为来自关系型数据库的数字. * @param jsonAttributes "{ \"from\" : " + "\"" + sid + "\",\" until\" : " + "\"" + oid + "\"}" * 其中:sid为起始节点的neo4j的id,oid为指向节点的neo4j的id. * @return * @throws URISyntaxException */ private URI addRelationship(URI startNode, URI endNode, String relationshipType, String jsonAttributes) throws URISyntaxException { URI fromUri = new URI(startNode.toString() + "/relationships"); String relationshipJson = generateJsonRelationship(endNode,relationshipType, jsonAttributes); WebResource resource = Client.create().resource(fromUri); ClientResponse response = resource.accept(MediaType.APPLICATION_JSON) .type(MediaType.APPLICATION_JSON).entity(relationshipJson) .post(ClientResponse.class); final URI location = response.getLocation(); response.close(); return location; }
4、获取节点nodes和节点nodeo之间的关系
URIrelationshipUri=addRelationship(nodes,nodeo,String.valueOf(p),"{\"from\":"+"\""+sid+"\",\"until\":"+"\""+oid+"\"}");
5、为关系relationshipUri添加"rel_sounce_id"属性
Stringrelationshipid="123";
addMetadataToProperty(relationshipUri,"rel_source_id",relationshipId);
具体的实现方法如下:
private void addMetadataToProperty(URI relationshipUri, String name,String value) throws URISyntaxException { URI propertyUri = new URI(relationshipUri.toString() + "/properties"); String entity = toJsonNameValuePairCollection(name, value); WebResource resource = Client.create().resource(propertyUri); ClientResponse response = resource.accept(MediaType.APPLICATION_JSON) .type(MediaType.APPLICATION_JSON).entity(entity) .put(ClientResponse.class); response.close(); }
4、更新(略)
由此可见,无论是增删还是改查,rest都是通过执行Cypher语言来操作neo4j数据库的,我们来介绍一下什么是Cypher语言:
1、通过Cypher创建节点:注意每个node,系统会自动建立一个唯一的id,不可修改的。
createn={name:'Motion',ID:'M001'}returnn;//这里的ID是节点的一个属性.
2、创建关系:
startn=node(14),m=node(20)createm-[r:KNOWS]-nreturnr;
3、查询:
按系统的id查询:startn=node(20)returnn;
查询所有节点的name:startn=node(*)returnn.name;
根据属性查找:startn=node(*)wheren.name='王新红'returnn;
按照关系查找:
1、查询所有与起始节点关联的信息
STARTn=node(14)MATCH(n)–[r]-(x)RETURNn,x(没有任何指向)
STARTn=node(14)MATCH(n)–[r]->(x)RETURNn,x(从n节点指向x节点)
STARTn=node(14)MATCH(n)<–[r]-(x)RETURNn,x(从x节点指向n节点)
2、查询定向关系
STARTn=node(14)MATCH(n)<-[r]-()RETURNr(查询所有指向n节点的relationship)
STARTn=node(14)MATCH(n)-[r]->()RETURNr(查询所有n节点指出的relationship)
3、路径远近查询
STARTn=node(14)MATCH(n)<–[r]-(x)<-[*1..3]-yRETURNn,x,y(查询所有指向n几点并且路径为2到4的关系)
starta=node(14),b=node(2472)matchp=a-[*0..4]-breturnp;(查询节点14和节点2472之间路径关系为0到4的所有节点)
分页查询:startn=node(*)returnnskip18limit3//每次skip相当于第几页,limit相当于每页多少条。
4、修改:
starta=node(*)wherea.name="a"seta.name="A"returna,a.name;
startn=node(0)setn.name="Root",n.id="001";
5、删除:
删除所有的节点和关系:startn=node(*)matchn-[r]-()deleten,r;
6、同事查询多个节点
starta=node(0),b=node(1)returna,b
二、嵌入式方式操作neo4j(利用一段程序了解其API)
private void copyInfoFromFactToNeo4j(){ GraphDatabaseService graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(DPATH); registerShutdownHook(graphDB); //发生意外的时候关闭,释放锁. try { Connection con = connectDB(); String sql = "select * from ont_fact o where o.neo4jRelationshipID is null and p in (select distinct id from ont_element where element_type=5) and p not in (46, 32,82,57,54,49,24,100,93,96,106)"; Statement st = con.createStatement(); ResultSet rs = st.executeQuery(sql); int i = 0; while (rs.next()) { //封装jdbc获取的关系型数据库的信息. Statement pst = con.createStatement(); int id = rs.getInt("id"); int s = rs.getInt("s"); int p = rs.getInt("p"); int o = rs.getInt("o"); int neo4jRelationId = rs.getInt("neo4jRelationshipID"); //neo4j开启事务 Transaction tx = graphDB.beginTx(); try { Index<Node> nodeIndex = graphDB.index().forNodes("nodes"); Index<Relationship> relIndex = graphDB.index().forRelationships("relationships"); //根据属性获取节点 Node node1 = getNode(graphDB, s); Node node2 = getNode(graphDB, o); //创建关系,并设置关系属性. RelationshipType reltype = DynamicRelationshipType.withName(p + ""); //创建节点之间的relationship关系 Relationship rel = node1.createRelationshipTo(node2,reltype); rel.setProperty("rel_source_id", id); relIndex.add(rel, "rel_source_id", id); //同步关系型数据库数据,使得图数据库与关系型数据库数据一致. String updatesql = "update [ont_fact] set neo4jRelationshipID='" + rel.getId() + "' where id=" + id; pst.executeUpdate(updatesql); tx.success(); } finally { tx.finish(); } } rs.close(); st.close(); con.close(); graphDB.shutdown(); } catch (Exception e) { e.printStackTrace(); } } private void registerShutdownHook(final GraphDatabaseService graphDB2) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { graphDB2.shutdown(); } }); } //获取节点 public Node getNode(GraphDatabaseService graphDB, int obj) { Node node; Transaction tx = graphDB.beginTx(); try { nodeIndex = graphDB.index().forNodes("nodes"); IndexHits<Node> node_results = nodeIndex.query("source_id", obj); // System.out.println(node_results.size()); if (node_results.size() > 0) { System.out.println("节点【" + obj + "】确实存在..."); node = node_results.getSingle(); } else { //如果不存在,则创建并添加节点属性信息. node = graphDB.createNode(); // long NodeId = node.getId(); node.setProperty("source_id", obj); nodeIndex.add(node, PRIMARY_KEY, node.getProperty("source_id")); } tx.success(); } finally { tx.finish(); } return node; }
总结:Neo4j对外提供两种方式,其中的嵌入式方式处理数据快,但是不能实现在线的备份和更新,必须先停掉neo4j服务。而rest方式则解决这一不足,但是处理数据的效率相对低下.