Neo4j图数据库使用

时间:2023-02-03 21:20:51

  最近在处理一些图的数据,主要是有向图,如果图的节点不是特别大可以直接加载到内存里来处理,但是当图的节点个数特别大时,内存就放不下了;我 们牵涉到的图的节点数最大可以达到数亿个节点,已经超出的机器内存的大小,所以必须把这些图的数据放到外存上,所以我们就选择了图数据库。

   尝试了2种图数据库,IBM System G 和 neo4j, 这两个数据库都可以处理上亿个节点的图,起始使用的是System G,但是存在一些问题,当图的节点数在300多万个,边数为1000多万个时,在创建图时就特别麻烦,程序老是创建不成功。后来就选择了 neo4j,neo4j是一个开源的图数据库,使用起来也比较方便,在创建比较大的图时速度远远超过System G;接下来把neo4j入门的知识记录下来,主要介绍neo4j嵌入在java开发中。

1、创建图(把图的数据存入neo4j)

  创建图由两种方法,一种是直接通过读取文件,在程序中显式的创建节点和边,另一种是通过加载CSV文件来创建。

1.1 程序中显示的创建图

  存放图的文件的格式如下图,以'v'开图的是顶点,后面的数字是它的id,id用从0开始顺序存放,在后面是label; 以'e'开头的行是边,后面第一个数字是边的起始点的id,第二个数字是边的终点的id,后面的字符串是边的label。

  Neo4j图数据库使用

  创建图的方法如下:

  

 1 public static void create_graph(GraphDatabaseService graph, File f) throws FileNotFoundException{
 2         Scanner scanner = new Scanner(f);
 3 
 4         while (scanner.hasNextLine()){
 5             String line = scanner.nextLine().trim();
 6 
 7             if (line.equals("") | line.startsWith("t")){
 8                 continue;
 9             } else if (line.startsWith("v")) {
10                 String nodeLabel = line.split(" ")[2];            //得到顶点的label
11                 Label label = DynamicLabel.label(nodeLabel);      //通过顶点的label,创建一个neo4j的Label类型,作为顶点的label, 这样就不用把label作为属性
12                 try (Transaction tx = graph.beginTx()){
13                     graph.createNode(label);                    //创建顶点
14                     tx.success();
15                 }
16             } else if (line.startsWith("e")) {
17                 String[] lineSplit = line.split(" ");
18                 int sourceId = Integer.parseInt(lineSplit[1]);        //得到变得起始顶点id和终止顶点id
19                 int targetId = Integer.parseInt(lineSplit[2]);
20                 String edgeLabel = lineSplit[3];                    //得到边的label
21                 try (Transaction tx = graph.beginTx()){
22                     Relationship edge = graph.getNodeById(sourceId).createRelationshipTo(graph.getNodeById(targetId), R.DIRECTED);  //创建边
23                     edge.setProperty("label", edgeLabel);    //给边设置属性
24                     tx.success();
25                 }
26             }
27         }
28         
29         scanner.close();
30     }

 

 

1.2 通过加载CSV文件来创建图

  如果使用CSV文件的话,需要通过URL来访问文件,我们使用两个URL,一个是顶点的URL,一个是边的URL,它们的文件格式要符合csv文件的格式.

  可以创建一个本地的apache服务器来存放这些文件,我们使用的顶点和边的url分别是:

  顶点url: http://127.0.0.1/nodes

  边url:  http://127.0.0.1/edges

然后存取顶点的代码如下:

1 String create_node = "USING PERIODIC COMMIT "
2         + "LOAD CSV WITH HEADERS FROM 'http://127.0.0.1/nodes' AS line "
3       + "CREATE (:node {label: line.label});";      //这样创建时,不能像上一种方法那样通过变量来指定label, 所以把label作为了顶点的属性了,第一个冒号前面可以指定顶点的名字,也可以不指定,冒号后面是该顶点的label.
4 graph.execute(create_node);    //执行cypher语言来创建结点

 

其中"USING PERIODIC COMMIT"的作用是分段式的创建顶点,可以认为指定读取多少行后就写入数据库,默认是读取1000行后写入数据库,例如"USING PERIODIC COMMIT 500",就是读取500行后就存入数据库.

存放边的代码如下:

1 String create_edge = "USING PERIODIC COMMIT "
2         + "LOAD CSV WITH HEADERS FROM 'http://127.0.0.1/edges' AS line "     
3         + "MATCH (p1), (p2) "    //找到边的两个顶点
4         + "WHERE id(p1)=toInt(line.source) and id(p2)=toInt(line.target) "
5         + "CREATE (p1)-[:DIRECTED {label: line.label}]->(p2);";   //创建边
6 graph.execute(create_edge);

 

其中,需要注意的是带有"USING PERIODIC COMMIT "的语句不能放在Transaction中执行,否则会出现如下的错误

“org.neo4j.cypher.PeriodicCommitInOpenTransactionException: Executing queries that use periodic commit in an open transaction is not possible.”

 

完整的创建顶点和边的方法如下:

 1 public static void create_nodes(GraphDatabaseService graph, String node_url) {        //创建顶点
 2         String create_node = "USING PERIODIC COMMIT "
 3                 + "LOAD CSV WITH HEADERS FROM " + node_url + "AS line "
 4                 + "CREATE (:node {label: line.label});";
 5         graph.execute(create_node);
 6         System.out.println("nodes create successfully!");
 7     }
 8 //创建边
 9 public static void create_edges(GraphDatabaseService graph, String edge_url){
10                 
11               String create_edge = "USING PERIODIC COMMIT "
12             + "LOAD CSV WITH HEADERS FROM " + edge_url + " AS line "
13             + "MATCH (p1), (p2) "
14             + "WHERE id(p1)=toInt(line.source) and id(p2)=toInt(line.target) "
15             + "CREATE (p1)-[:DIRECTED {label: line.label}]->(p2);";
16         graph.execute(create_edge);
17 
18         System.out.println("edges create successfully!");
19     } 

 

 

2.得到一个顶点的所有出边的终点的id

 1 public static ArrayList<Long> get_out_nodes(GraphDatabaseService graph, Node node){
 2         ArrayList<Long> out = new ArrayList<Long>();
 3         try (Transaction tx = graph.beginTx()){
 4             Traverser tr;
 5             TraversalDescription td = graph.traversalDescription()
 6                     .breadthFirst()
 7                     .relationships(R.DIRECTED, Direction.OUTGOING)
 8                     .evaluator(Evaluators.excludeStartPosition());
 9             tr = td.traverse(node);
10             for (Path path : tr){
11                 if (path.length() == 1){
12                     out.add(path.endNode().getId());
13                 }
14             }
15             tx.success();
16         }
17         return out;
18     }

 

 3.得到一个顶点的所有入边的起始点的id

  

 1  public static ArrayList<Long> get_in_nodes(GraphDatabaseService graph, Node node){
 2         ArrayList<Long> in = new ArrayList<Long>();
 3         try (Transaction tx = graph.beginTx()){
 4             Traverser tr;
 5             TraversalDescription td = graph.traversalDescription()
 6                     .breadthFirst()
 7                     .relationships(R.DIRECTED, Direction.INCOMING)
 8                     .evaluator(Evaluators.excludeStartPosition());
 9             tr = td.traverse(node);
10             for (Path path : tr){
11                 if (path.length() == 1){
12                     in.add(path.endNode().getId());
13                 }
14             }
15             tx.success();
16         }
17         return in;
18     }

 

 

4.得到图中所有顶点的个数

 1 public static int getSize(GraphDatabaseService graph){
 2         int size = 0;
 3         try (Transaction tx = graph.beginTx()){
 4             Iterator<Node> it = graph.getAllNodes().iterator();
 5             while(it.hasNext()){
 6                 size++;
 7                 it.next();
 8             }
 9             tx.success();
10         }
11         return size;
12     }

 

 

5.根据顶点的属性label的值,得到具有相同label值的顶点的个数

 1  public static int getSizeByLabel(GraphDatabaseService graph, String label){
 2         try(Transaction tx = graph.beginTx()){
 3             Label node = DynamicLabel.label("node");         //在创建顶点时,指定了顶点的label为"node",注意这个label是Label类型的,与顶点属性的label不一样
 4             ResourceIterator<Node> result = graph.findNodes(node, "label", label);
 5             ArrayList<Node> nodes = new ArrayList<>();
 6             while (result.hasNext()){
 7                 nodes.add(result.next());
 8             }
 9             tx.success();
10             return nodes.size();
11         }
12     }

 

 

6. 给出顶点的id,得到该顶点某个属性的值,如label属性的值

1 public static String getNodeLabel(GraphDatabaseService graph, int id){
2         try(Transaction tx = graph.beginTx()){
3             String nodeLabel = graph.getNodeById(id).getProperties("label").toString();    //返回的值的样式如下:{label=AND2X1}
4             String label = nodeLabel.substring(7, nodeLabel.length()-1);      //对上一步的返回值进行取子串
5             tx.success();
6             return label;
7         }
8     }

 

就先介绍这些基本的操作吧,以后用到新的操作了在做补充!

 

参考链接入下:

neo4j官方教程