Skip to content

Commit 8a251b5

Browse files
final draft
.
1 parent 54edf6c commit 8a251b5

File tree

2 files changed

+159
-39
lines changed

2 files changed

+159
-39
lines changed

java-manual/modules/ROOT/pages/query-simple.adoc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Once you have xref:connect.adoc[connected to the database], you can execute <<Cy
77
For queries with earlier versions, use xref:transactions.adoc[sessions and transactions].
88

99

10+
[#write]
1011
== Write to the database
1112

1213
To create two nodes representing persons named `Alice` and `David`, and a relationship `KNOWS` between them, use the Cypher clause link:{neo4j-docs-base-uri}/cypher-manual/current/clauses/create/[`CREATE`]:
@@ -38,6 +39,7 @@ System.out.printf("Created %d records in %d ms.%n",
3839
<4> The xref:result-summary.adoc[summary of execution] returned by the server
3940

4041

42+
[#read]
4143
== Read from the database
4244

4345
To retrieve information from the database, use the Cypher clause link:{neo4j-docs-base-uri}/cypher-manual/current/clauses/match/[`MATCH`]:
@@ -71,16 +73,15 @@ System.out.printf("The query %s returned %d records in %d ms.%n",
7173
<1> `records` contains the result as a list of link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/Record.html[`Record`] objects
7274
<2> `summary` contains the xref:result-summary.adoc[summary of execution] returned by the server
7375

74-
[TIP]
75-
====
7676
Properties inside a link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/Record.html[`Record`] object are embedded within link:https://neo4j.com/docs/api/java-driver/{java-driver-version}/org.neo4j.driver/org/neo4j/driver/Value.html[`Value`] objects.
7777
To extract and cast them to the corresponding Java types, use `.as<type>()` (eg. `.asString()`, `asInt()`, etc).
7878
For example, if the `name` property coming from the database is a string, `record.get("name").asString()` will yield the property value as a `String` object.
79-
8079
For more information, see xref:data-types.adoc[].
81-
====
80+
81+
Another way of extracting values from returned records is by xref:value-mapping.adoc[mapping them objects].
8282

8383

84+
[#update]
8485
== Update the database
8586

8687
To update a node's information in the database, use the Cypher clauses link:{neo4j-docs-base-uri}/cypher-manual/current/clauses/match/[`MATCH`] and link:{neo4j-docs-base-uri}/cypher-manual/current/clauses/set/[`SET`]:
@@ -129,7 +130,9 @@ System.out.println(summary.counters().containsUpdates());
129130
<3> Create a new `:KNOWS` relationship outgoing from the node bound to `alice` and attach to it the `Person` node named `Bob`
130131

131132

133+
[#delete]
132134
== Delete from the database
135+
133136
To remove a node and any relationship attached to it, use the Cypher clause link:{neo4j-docs-base-uri}/cypher-manual/current/clauses/delete/[`DETACH DELETE`]:
134137

135138
.Remove the `Alice` node and all its relationships
Lines changed: 152 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,179 @@
11
= Map query results to objects
22

3-
with execquery you need to extract props and cast them ahead
3+
When xref:query-simple.adoc#read[working with values coming from a query result], you have to manually extract their properties and cast them to the relevant Java types before you can use them.
4+
For example, to retrieve the `name` property as a string, you have to do `person.get("name").asString()`.
5+
6+
With the driver's Value Mapping feature, you can declare a class containing the specification of the values your query is expected to return, and ask the driver to use that class to spawn new objects from a query result.
47

5-
[source, java]
6-
----
7-
var result = driver.executableQuery("""
8-
MATCH (p:Person)-[:KNOWS]->(:Person)
9-
RETURN p.name AS name
10-
""")
11-
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
12-
.execute();
13-
var records = result.records();
14-
records.forEach(r -> {
15-
System.out.println(
16-
"Person %s is %d years old."
17-
r.get("name").asString()
18-
r.get("age").asInt()
19-
);
20-
});
21-
----
228

9+
== Map driver values to a local class
2310

11+
To map records into objects, define a class having the same attributes as the keys returned by the query.
12+
**The constructor arguments must match exactly the query return keys**, and they are case-sensitive.
2413

14+
Your class should be defined as a link:https://docs.oracle.com/en/java/javase/17/language/records.html[Java Record], and you provide its definition through the link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/Value.html#as(java.lang.Class)[`Value.as()`] method.
15+
16+
.Map `:Person` nodes onto a `Person` record class
2517
[source, java]
2618
----
2719
package demo;
2820
29-
import java.util.Map;
30-
import java.util.List;
31-
import java.util.concurrent.TimeUnit;
32-
3321
import org.neo4j.driver.AuthTokens;
3422
import org.neo4j.driver.Driver;
3523
import org.neo4j.driver.GraphDatabase;
36-
import org.neo4j.driver.Record;
3724
import org.neo4j.driver.QueryConfig;
38-
import org.neo4j.driver.RoutingControl;
3925
4026
public class App {
4127
4228
private static final String URI = "neo4j://localhost:7687";
4329
private static final String USER = "neo4j";
4430
private static final String PASSWORD = "verysecret";
4531
46-
public record Person(String name, int age) {}
47-
4832
public static void main(String... args) {
49-
5033
try (var driver = GraphDatabase.driver(URI, AuthTokens.basic(USER, PASSWORD))) {
51-
var movies = driver.executableQuery("MATCH (p:Person) RETURN p")
52-
.execute()
53-
.records()
54-
.stream()
55-
.map(record -> record.get("p").as(Movie.class))
56-
.toList();
57-
58-
System.out.printf(movies.get(0).title);
34+
record Person(String name, Integer age) {}
35+
var persons = driver.executableQuery("MERGE (p:Person {name: 'Margarida', age: 29}) RETURN p")
36+
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
37+
.execute()
38+
.records()
39+
.stream()
40+
.map(record -> record.get("p").as(Person.class))
41+
.toList();
42+
System.out.println(persons.get(0)); // Person[name=Margarida, age=29]
5943
}
6044
}
6145
}
6246
----
47+
48+
Declaring the `record` object side-by-side with the query that uses it is a convenient way to obtain results on which it is easy to extract properties.
49+
However, because the class is defined in a local scope, you can't return the mapped values directly: you need to process them in the same function, or manually build another object containing the properties you want to return.
50+
Another solution is to declare the `record` object as a `public` member of the class, or to create a new standalone class containing your `record` definition.
51+
This will make the mapped object available out of the scope of the method in which is was defined.
52+
53+
[NOTE]
54+
====
55+
Constructor arguments with generic complex types are not supported.
56+
57+
[source, java, test-skip]
58+
----
59+
public record Friends<T>(List<T> names) {}
60+
----
61+
62+
Constructor arguments with specific complex types are permitted.
63+
[source, java, test-skip]
64+
----
65+
public record Friends(List<String> names) {}
66+
----
67+
====
68+
69+
70+
== Map properties with different names (`@Property`)
71+
72+
A record's property names and its query return keys can be different.
73+
For example, consider a node `(:Person {name: "Alice"})`.
74+
The returned keys for the query `MERGE (p:Person {name: "Alice"}) RETURN p.name` are `p.name`, even if the property name is `name`.
75+
Similarly, for the query `MERGE (pers:Person {name: "Alice"}) RETURN pers.name`, the return keys are `pers.name`.
76+
77+
You can always alter the return key with the Cypher operator link:https://neo4j.com/docs/cypher-manual/current/clauses/return/#return-column-alias[`AS`] (ex. `MERGE (p:Person {name: "Alice"}) RETURN p.name AS name`), or use the `@Property(<dbPropertyName>)` decorator to specify the property name that the given constructor argument should map to.
78+
79+
.Properties `name`/`age` are mapped to the object attributes `Name`/`Age`
80+
[source, java]
81+
----
82+
// import org.neo4j.driver.mapping.Property;
83+
84+
record Person(@Property("name") String Name, @Property("age") Integer Age) {}
85+
var persons = driver.executableQuery("MERGE (p:Person {name: 'Margarida', age: 29}) RETURN p")
86+
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
87+
.execute()
88+
.records()
89+
.stream()
90+
.map(record -> record.get("p").as(Person.class))
91+
.toList();
92+
System.out.println(persons.get(0)); // Person[Name=Margarida, Age=29]
93+
----
94+
95+
96+
== Map driver records to a local class
97+
98+
The earlier examples have mapped a driver value (for example a node identified with `p`) to a class.
99+
You can also use the mapping feature with driver records, through the link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/Record.html#as(java.lang.Class)[`Record.as()`] method.
100+
101+
.Return keys `name`/`p.age` are mapped to the object attributes `Name`/`Age`
102+
[source, java]
103+
----
104+
// import org.neo4j.driver.mapping.Property;
105+
106+
record Person(String name, @Property("p.age") Integer age) {}
107+
var persons = driver.executableQuery("""
108+
MERGE (p:Person {name: 'Margarida', age: 29})
109+
RETURN p.name AS name, p.age
110+
""")
111+
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
112+
.execute()
113+
.records()
114+
.stream()
115+
.map(record -> record.as(Person.class))
116+
.toList();
117+
System.out.println(persons.get(0)); // Person[name=Margarida, age=29]
118+
----
119+
120+
121+
== Work with multiple constructors
122+
123+
Your class can contain multiple constructors.
124+
In that case, the driver picks one basing on the following criteria (in order of priority):
125+
126+
- Most matching properties
127+
- Least mis-matching properties
128+
129+
At least one property must match for a constructor to work with the mapping.
130+
131+
.An additional constructor to handle the optional `age` property
132+
[source, java]
133+
----
134+
// import org.neo4j.driver.mapping.Property;
135+
136+
record Person(String name, int age) {
137+
public Person(@Property("name") String name) {
138+
this(name, -1);
139+
}
140+
}
141+
var persons = driver.executableQuery("MERGE (p:Person {name: 'Axel'}) RETURN p")
142+
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
143+
.execute()
144+
.records()
145+
.stream()
146+
.map(record -> record.get("p").as(Person.class))
147+
.toList();
148+
----
149+
150+
[NOTE]
151+
====
152+
The compiler renames constructor parameters by default, unless the compiler `-parameters` option is used or the parameters belong to the cannonical constructor of `java.lang.Record`.
153+
154+
In the example above, the constructor containing only `name` uses the `@Property` annotation even if it doesn't specify a different name than the constructor argument. This is needed because that is not the canonical constructor.
155+
====
156+
157+
158+
== Insert and update data
159+
160+
You can also use the mapping feature to insert or update data, by creating an instance of the `record` object that serves as a blueprint for your object and then passing it to the query as a parameter.
161+
162+
.Create and update a `:Person` node
163+
[source, java]
164+
----
165+
record Person(String name, int age) {}
166+
167+
var person = new Person("Lucia", 29);
168+
driver.executableQuery("CREATE (:Person $person)")
169+
.withParameters(Map.of("person", person))
170+
.execute();
171+
172+
var happyBirthday = new Person("Lucia", 30);
173+
driver.executableQuery("""
174+
MATCH (p:Person {name: $person.name})
175+
SET person += $person
176+
""")
177+
.withParameters(Map.of("person", happyBirthday))
178+
.execute();
179+
----

0 commit comments

Comments
 (0)