В первой части мы говорили о настройке соединения с сервером Neo4j с помощью привязки Java REST. Теперь давайте подробно рассмотрим транзакции, пакетирование и то, как на самом деле выглядят запросы REST. Убедитесь, что вы включили ведение журнала (установите для системного свойства org.neo4j.rest.logging_filter to true) as described in Part 1
.
Мы изменим наш код для выполнения этих вызовов API Neo4j.
Пример 1:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
Transaction tx = graphDb.beginTx(); Map props= new HashMap(); props.put( "id" , 100 ); props.put( "name" , "firstNode" ); Node node=graphDb.createNode(props); props.put( "id" , 200 ); props.put( "name" , "secondNode" ); Node node2=graphDb.createNode(props); node.createRelationshipTo(node2, DynamicRelationshipType.withName( "KNOWS" )); tx.success(); tx.finish(); result=engine.query( "start n=node(*) return count(n) as total" , Collections.EMPTY_MAP); Iterator iterator=result.iterator(); if (iterator.hasNext()) { Map row= iterator.next(); out.print( "Total nodes: " + row.get( "total" )); } |
Проверьте журналы (для меня они появились по умолчанию на консоли Tomcat) и найдите вызовы REST. Код выше производится:
01
02
03
04
05
06
07
08
09
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
INFO: 1 * Client out-bound request 1 > POST http: //localhost :7474 /db/data/node 1 > Accept: application /json ; stream= true 1 > X-Stream: true 1 > Content-Type: application /json 1 > { "id" :100, "name" : "firstNode" } INFO: 1 * Client in -bound response 1 < 201 1 < Access-Control-Allow-Origin: * 1 < Transfer-Encoding: chunked 1 < Content-Encoding: UTF-8 1 < Location: http: //localhost :7474 /db/data/node/1 1 < Content-Type: application /json ; stream= true 1 < Server: Jetty(6.1.25) 1 < { "extensions" :{}, "paged_traverse" : "http://localhost:7474/db/data/node/1/paged/traverse/{returnType}{?pageSize,leaseTime}" , "outgoing_relationships" : "http://localhost:7474/db/data/node/1/relationships/out" , "traverse" : "http://localhost:7474/db/data/node/1/traverse/{returnType}" , "all_typed_relationships" : "http://localhost:7474/db/data/node/1/relationships/all/{-list|&|types}" , "property" : "http://localhost:7474/db/data/node/1/properties/{key}" , "all_relationships" : "http://localhost:7474/db/data/node/1/relationships/all" , "self" : "http://localhost:7474/db/data/node/1" , "properties" : "http://localhost:7474/db/data/node/1/properties" , "outgoing_typed_relationships" : "http://localhost:7474/db/data/node/1/relationships/out/{-list|&|types}" , "incoming_relationships" : "http://localhost:7474/db/data/node/1/relationships/in" , "incoming_typed_relationships" : "http://localhost:7474/db/data/node/1/relationships/in/{-list|&|types}" , "create_relationship" : "http://localhost:7474/db/data/node/1/relationships" , "data" :{ "name" : "firstNode" , "id" :100}} INFO: 2 * Client out-bound request 2 > POST http: //localhost :7474 /db/data/node 2 > Accept: application /json ; stream= true 2 > X-Stream: true 2 > Content-Type: application /json 2 > { "id" :200, "name" : "secondNode" } INFO: 2 * Client in -bound response 2 < 201 2 < Access-Control-Allow-Origin: * 2 < Transfer-Encoding: chunked 2 < Content-Encoding: UTF-8 2 < Location: http: //localhost :7474 /db/data/node/2 2 < Content-Type: application /json ; stream= true 2 < Server: Jetty(6.1.25) 2 < { "extensions" :{}, "paged_traverse" : "http://localhost:7474/db/data/node/2/paged/traverse/{returnType}{?pageSize,leaseTime}" , "outgoing_relationships" : "http://localhost:7474/db/data/node/2/relationships/out" , "traverse" : "http://localhost:7474/db/data/node/2/traverse/{returnType}" , "all_typed_relationships" : "http://localhost:7474/db/data/node/2/relationships/all/{-list|&|types}" , "property" : "http://localhost:7474/db/data/node/2/properties/{key}" , "all_relationships" : "http://localhost:7474/db/data/node/2/relationships/all" , "self" : "http://localhost:7474/db/data/node/2" , "properties" : "http://localhost:7474/db/data/node/2/properties" , "outgoing_typed_relationships" : "http://localhost:7474/db/data/node/2/relationships/out/{-list|&|types}" , "incoming_relationships" : "http://localhost:7474/db/data/node/2/relationships/in" , "incoming_typed_relationships" : "http://localhost:7474/db/data/node/2/relationships/in/{-list|&|types}" , "create_relationship" : "http://localhost:7474/db/data/node/2/relationships" , "data" :{ "name" : "secondNode" , "id" :200}} INFO: 3 * Client out-bound request 3 > POST http: //localhost :7474 /db/data/node/1/relationships 3 > Accept: application /json ; stream= true 3 > X-Stream: true 3 > Content-Type: application /json 3 > INFO: 3 * Client in -bound response 3 < 201 3 < Access-Control-Allow-Origin: * 3 < Transfer-Encoding: chunked 3 < Content-Encoding: UTF-8 3 < Location: http: //localhost :7474 /db/data/relationship/0 3 < Content-Type: application /json ; stream= true 3 < Server: Jetty(6.1.25) 3 < { "extensions" :{}, "start" : "http://localhost:7474/db/data/node/1" , "property" : "http://localhost:7474/db/data/relationship/0/properties/{key}" , "self" : "http://localhost:7474/db/data/relationship/0" , "properties" : "http://localhost:7474/db/data/relationship/0/properties" , "type" : "KNOWS" , "end" : "http://localhost:7474/db/data/node/2" , "data" :{}} INFO: 4 * Client out-bound request 4 > POST http: //localhost :7474 /db/data/cypher 4 > Accept: application /json ; stream= true 4 > X-Stream: true 4 > Content-Type: application /json 4 > { "query" : "start n=node(*) return count(n) as total" , "params" :{}} INFO: 4 * Client in -bound response 4 < 200 4 < Access-Control-Allow-Origin: * 4 < Transfer-Encoding: chunked 4 < Content-Encoding: UTF-8 4 < Content-Type: application /json ; stream= true 4 < Server: Jetty(6.1.25) 4 < { "columns" :[ "total" ], "data" :[[3]]} |
В общей сложности 4 вызова REST по проводам для этого крошечного куска кода. Вы определенно хотите избежать этого, насколько это возможно. Вариант 1 — использовать Cypher, насколько это возможно. Мы могли бы преобразовать первые три вызова REST в один, не используя API встроенного стиля и переключившись на Cypher.
Пример 2:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
Map<String,Object> props= new HashMap<String, Object>(); props.put( "id" , 100 ); props.put( "name" , "firstNode" ); Map<String,Object> props2= new HashMap<String, Object>(); props2.put( "id" , 200 ); props2.put( "name" , "secondNode" ); Map<String,Object> params= new HashMap<String, Object>(); params.put( "props1" ,props); params.put( "props2" ,props2); engine.query( "create (n1 {props1})-[:KNOWS]->(n2 {props2})" , params); |
Это производит:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
1 > POST http: //localhost :7474 /db/data/cypher { "query" : "create (n1 {props1})-[:KNOWS]->(n2 {props2})" , "params" :{ "props1" :{ "id" :100, "name" : "firstNode" }, "props2" :{ "id" :100, "name" : "firstNode" }}} Jul 24, 2013 10:38:47 PM com.sun.jersey.api.client.filter.LoggingFilter log INFO: 1 * Client in -bound response 1 < 200 1 < Access-Control-Allow-Origin: * 1 < Transfer-Encoding: chunked 1 < Content-Encoding: UTF-8 1 < Content-Type: application /json ; stream= true 1 < Server: Jetty(6.1.25) 1 < { "columns" :[], "data" :[]} |
Пакетирование всех операций внутри транзакции
Документация по адресу https://github.com/neo4j/java-rest-binding гласит:
«В 1.8 он пытается собрать все операции внутри tx как пакетную операцию, которая затем будет выполнена на сервере. Это означает, что результаты, полученные в этом «tx», не сразу доступны, а только после того, как вы вызвали tx.success и tx.finish «
Однако обратите внимание, что это НЕ поведение по умолчанию, как вы можете видеть из примера 1. Чтобы включить это, вам нужно установить следующее системное свойство: org.neo4j.rest.batch_transaction=true
После установки системного свойства и повторного запуска примера 1 вызовы REST выглядят так (только запросы):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
INFO: 1 * Client out-bound request 1 > POST http: //localhost :7474 /db/data/batch 1 > Accept: application /json ; stream= true 1 > X-Stream: true 1 > Content-Type: application /json 1 > [{ "id" :1, "to" : "node" , "body" :{ "id" :200, "name" : "secondNode" }, "method" : "POST" },{ "id" :2, "to" : "node" , "body" :{ "id" :200, "name" : "secondNode" }, "method" : "POST" },{ "id" :3, "to" : "{1}/relationships" , "body" :{ "to" : "{2}" , "type" : "KNOWS" }, "method" : "POST" }] INFO: 2 * Client out-bound request 2 > POST http: //localhost :7474 /db/data/cypher 2 > Accept: application /json ; stream= true 2 > X-Stream: true 2 > Content-Type: application /json 2 > { "query" : "start n=node(*) return count(n) as total" , "params" :{}} |
Вы также можете явно создавать пакетные операции следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
List<Node> response =graphDb.executeBatch( new BatchCallback<List<Node>>() { @Override public List<Node> recordBatch(RestAPI batchRestApi) { List<Node> nodes= new ArrayList<Node>(); Map props= new HashMap<String, Object>(); props.put( "id" , 600 ); nodes.add(batchRestApi.createNode(props)); Map props2= new HashMap<String, Object>(); props2.put( "id" , 500 ); nodes.add(batchRestApi.createNode(props2)); return nodes; } }); |
Это переводится на:
1
2
3
4
5
6
7
|
INFO: 1 * Client out-bound request 1 > POST http: //localhost :7474 /db/data/batch 1 > Accept: application /json ; stream= true 1 > X-Stream: true 1 > Content-Type: application /json 1 > [{ "id" :1, "to" : "node" , "body" :{ "id" :600}, "method" : "POST" },{ "id" :2, "to" : "node" , "body" :{ "id" :500}, "method" : "POST" }] |
Любой из подходов Cypher / Batching настоятельно рекомендуется для более тонкого Java Neo4j API. В последнем посте мы рассмотрим поведение транзакций в контексте привязки REST.