NOSQL数据库之neo4j

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方式则解决这一不足,但是处理数据的效率相对低下.

相关推荐