NOSQL的图数据库之Neo4j的学习:
neo4j官方表示性能良好,支持上亿级别数量的节点,但是成熟的资料不多。
1、在安装的过程中对jdk有一定的要求,我所安装的neo4j版本就要求jdk是1.7的。
2、对于neo4j的使用有两种方式:一种是嵌入式方式,另一种是提供了Rest访问的
服务器模式。
一、 Rest方式操作Neo4j:
这里应用了Jersey,通过Jersey客户端API调用rest风格的Web服务,这样就不用
关闭neo4j的服务,进行相关数据的增删改查了。
final String SERVER_ROOT_URI = "http://10.0.11.144:7474/db/data/";
1、查询
2、删除
neo4j的删除,必须先删除其relationship,再删除其节点,否则删除失败!
这里根据source_id = 123进行删除节点,同时也可以进行批量删除:where n.source_id = 123 OR n.source_id = 124 OR n.source_id = 156。
但是应注意:因为是rest方式进行删除,这些参数不能拼接的太长,否则会报错.一般控制在几十个左右。
3、添加:
注意:在采用rest这种增量式的添加的时候,由于新增的数据与已经存在的一致,但是再次添加的时候,
neo4j仍然能够正常的添加进去,以为neo4j是根据自己维护节点的id的,所以在增加新的数据之前,很有必要
对数据是否已在neo4j中创建进行判断.
//这是创建的新节点,返回的URI是什么呢?我们看一下获取已经存在的节点的URI
1、利用上面创建新节点的方法创建两个节点.
URI nodes = createNode();
URI nodeo = createNode();
2、为这两个节点增加source_id属性.
addProperty(nodes,"source_id",99);
addProperty(nodeo,"source_id",100);
方法addProperty的具体实现如下:
3、创建好了两个节点并为其增加了属性,那么如何为他们添加关联关系呢.
4、获取节点nodes和节点nodeo之间的关系
URI relationshipUri = addRelationship(nodes, nodeo, String.valueOf(p), "{ \"from\" : " + "\"" + sid + "\",\" until\" : " + "\"" + oid + "\"}");
5、为关系relationshipUri添加"rel_sounce_id"属性
String relationshipId = "123";
addMetadataToProperty(relationshipUri, "rel_source_id", relationshipId);
具体的实现方法如下:
4、更新(略)
由此可见,无论是增删还是改查,rest都是通过执行Cypher语言来操作neo4j数据库的,我们来介绍一下什么是Cypher语言:
1、通过Cypher创建节点:注意每个node,系统会自动建立一个唯一的id,不可修改的。
create n={name:'Motion',ID:'M001'} return n; //这里的ID是节点的一个属性.
2、创建关系:
start n=node(14),m=node(20) create m-[r:KNOWS]-n return r;
3、查询:
按系统的id查询: start n = node(20) return n;
查询所有节点的name: start n = node(*) return n.name;
根据属性查找: start n = node(*) where n.name = '王新红' return n;
按照关系查找:
1、查询所有与起始节点关联的信息
START n=node(14) MATCH (n)–[r]-(x) RETURN n,x (没有任何指向)
START n=node(14) MATCH (n)–[r]->(x) RETURN n,x (从n节点指向x节点)
START n=node(14) MATCH (n)<–[r]-(x) RETURN n,x (从x节点指向n节点)
2、查询定向关系
START n=node(14) MATCH (n)<-[r]-() RETURN r (查询所有指向n节点的relationship)
START n=node(14) MATCH (n)-[r]->() RETURN r (查询所有n节点指出的relationship)
3、路径远近查询
START n=node(14) MATCH (n)<–[r]-(x)<-[*1..3]-y RETURN n,x,y (查询所有指向n几点并且路径为2到4的关系)
start a=node(14),b=node(2472) match p=a-[*0..4]-b return p; (查询节点14和节点2472之间路径关系为0到4的所有节点)
分页查询: start n=node(*) return n skip 18 limit 3 //每次skip相当于第几页,limit相当于每页多少条。
4、修改:
start a = node(*) where a.name="a" set a.name="A" return a,a.name ;
start n=node(0) set n.name="Root",n.ID="001" ;
5、删除:
删除所有的节点和关系:start n = node(*) match n-[r]-() delete n,r;
6、同事查询多个节点
start a = node(0),b = node(1) return a,b
二、嵌入式方式操作neo4j(利用一段程序了解其API)
总结:Neo4j对外提供两种方式,其中的嵌入式方式处理数据快,但是不能实现在线的备份和更新,必须先停掉neo4j服务。而rest方式则解决这一不足,但是处理数据的效率相对低下.
neo4j官方表示性能良好,支持上亿级别数量的节点,但是成熟的资料不多。
1、在安装的过程中对jdk有一定的要求,我所安装的neo4j版本就要求jdk是1.7的。
2、对于neo4j的使用有两种方式:一种是嵌入式方式,另一种是提供了Rest访问的
服务器模式。
一、 Rest方式操作Neo4j:
这里应用了Jersey,通过Jersey客户端API调用rest风格的Web服务,这样就不用
关闭neo4j的服务,进行相关数据的增删改查了。
final String SERVER_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进行删除节点,同时也可以进行批量删除:where n.source_id = 123 OR n.source_id = 124 OR n.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、利用上面创建新节点的方法创建两个节点.
URI nodes = createNode();
URI nodeo = 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之间的关系
URI relationshipUri = addRelationship(nodes, nodeo, String.valueOf(p), "{ \"from\" : " + "\"" + sid + "\",\" until\" : " + "\"" + oid + "\"}");
5、为关系relationshipUri添加"rel_sounce_id"属性
String relationshipId = "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,不可修改的。
create n={name:'Motion',ID:'M001'} return n; //这里的ID是节点的一个属性.
2、创建关系:
start n=node(14),m=node(20) create m-[r:KNOWS]-n return r;
3、查询:
按系统的id查询: start n = node(20) return n;
查询所有节点的name: start n = node(*) return n.name;
根据属性查找: start n = node(*) where n.name = '王新红' return n;
按照关系查找:
1、查询所有与起始节点关联的信息
START n=node(14) MATCH (n)–[r]-(x) RETURN n,x (没有任何指向)
START n=node(14) MATCH (n)–[r]->(x) RETURN n,x (从n节点指向x节点)
START n=node(14) MATCH (n)<–[r]-(x) RETURN n,x (从x节点指向n节点)
2、查询定向关系
START n=node(14) MATCH (n)<-[r]-() RETURN r (查询所有指向n节点的relationship)
START n=node(14) MATCH (n)-[r]->() RETURN r (查询所有n节点指出的relationship)
3、路径远近查询
START n=node(14) MATCH (n)<–[r]-(x)<-[*1..3]-y RETURN n,x,y (查询所有指向n几点并且路径为2到4的关系)
start a=node(14),b=node(2472) match p=a-[*0..4]-b return p; (查询节点14和节点2472之间路径关系为0到4的所有节点)
分页查询: start n=node(*) return n skip 18 limit 3 //每次skip相当于第几页,limit相当于每页多少条。
4、修改:
start a = node(*) where a.name="a" set a.name="A" return a,a.name ;
start n=node(0) set n.name="Root",n.ID="001" ;
5、删除:
删除所有的节点和关系:start n = node(*) match n-[r]-() delete n,r;
6、同事查询多个节点
start a = node(0),b = node(1) return a,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方式则解决这一不足,但是处理数据的效率相对低下.