The Neo4j Java Driver provides you with three APIs for consuming results:
-
Synchronous API
-
Async API
-
Reactive API
The Three APIs
The most common and straightforward method of consuming results is with the synchronous API.
When using session.run()
, tx.run()
, or one of the two transaction functions, the query will return a Result
object that you can process incrementally and then return the results of that processing.
For the asynchronous and reactive APIs you need to use different entry-points and API methods and helpers like a reactive framework. In return you get more efficient resource usage in the database, middleware and client by using the non-synchronous APIs.
try (var session = driver.session()) {
var res = session.readTransaction(tx -> tx.run(
"MATCH (p:Person) RETURN p.name AS name LIMIT 10").list());
res.stream()
.map(row -> row.get("name"))
.forEach(System.out::println);
} catch (Exception e) {
// There was a problem with the
// database connection or the query
e.printStackTrace();
}
var session = driver.asyncSession();
session.readTransactionAsync(tx -> tx.runAsync(
"MATCH (p:Person) RETURN p.name AS name LIMIT 10")
.thenApplyAsync(res -> res.listAsync(row -> row.get("name")))
.thenAcceptAsync(System.out::println)
.exceptionallyAsync(e -> {
e.printStackTrace();
return null;
})
);
Flux.usingWhen(Mono.fromSupplier(driver::rxSession),
session -> session.readTransaction(tx -> {
var rxResult = tx.run(
"MATCH (p:Person) RETURN p.name AS name LIMIT 10");
return Flux
.from(rxResult.records())
.map(r -> r.get("name").asString())
.doOnNext(System.out::println)
.then(Mono.from(rxResult.consume()));
}
), RxSession::close);
The Result
The Result
object, contains the records received by the Driver along with a set of additional meta data about the query execution and results.
An individual row of results is referred to as a Record
, and can be accessed from the result various ways, as Iterator<Record>
and via the stream()
, the list()
or single()
methods.
A Record
refers to the keyed set of values specified in the RETURN
portion of the statement.
If no RETURN
values are specified, the query will not return any results, and record results will be empty or throw an error in the case of single()
.
Additional meta data about the result and query is accessible from Result
too (see below).
Records
You can access the records returned by the query through several means.
A Result
is an Iterator<Record>
there are stream()
and list()
accessors for streaming and materialization/conversion.
// Get single row, error when more/less than 1
Record row = res.single();
// Materialize list
List<Record> rows = res.list();
// Stream results
Stream<Record> rowStream = res.stream();
// iterate (sorry no foreach)
while (res.hasNext()) {
var next = res.next();
}
Key or Index
You can either access a value within the record by using the alias as specified in theRETURN
portion of the Cypher statement or by specifying the column index (not recommended).
The available keys can be accessed through res.keys()
.// column names
row.keys();
// check for existence
row.containsKey("movie");
// number of columns
row.size();
// get a numeric value (int, long, float, double also possible)
Number count = row.get("movieCount").asInt(0);
// get a boolean value
boolean isDirector = row.get("isDirector").asBoolean();
// get node
row.get("movie").asNode();
Result Summary
The meta data ResultSummary
accessed from Result.consume()
include
-
statistics on how many nodes and relationships were created, updated, or deleted as a result of the query,
-
the query type
-
database and server info
-
query plan with and without profile
-
notifications
You can find more detail in the API docs for ResultSummary
For example, to get information about how long the query took to complete, you can use the following property:
ResultSummary summary = res.consume();
// Time in milliseconds before receiving the first result
summary.resultAvailableAfter(TimeUnit.MILLISECONDS); // 10
// Time in milliseconds once the final result was consumed
summary.resultConsumedAfter(TimeUnit.MILLISECONDS); // 30
Another interesting part of the summary is the SummaryCounters
available via counters()
, which has update counts about a write-statement’s execution.
You can check via counters.containsUpdates()
if there were any updates.
SummaryCounters counters = summary.counters();
// some example counters
// nodes and relationships
counters.containsUpdates();
counters.nodesCreated();
counters.labelsAdded();
counters.relationshipsDeleted();
counters.propertiesSet();
// indexes and constraints
counters.indexesAdded();
counters.constraintsRemoved();
// updates to system db
counters.containsSystemUpdates();
counters.systemUpdates();
Check Your Understanding
1. What is the drawback of using the synchronous API to consume results?
-
❏ The synchronous API is only available to Enterprise customers.
-
✓ Results are only available once the Driver has received the final record.
-
❏ You can only use the synchronous API within a Read Transactions.
Hint
If you are not subscribing to the record stream, you will only be able to access the first record once the entire stream has finished.
Solution
Results are only available once the Driver has received the final record. This can provide a negative experience for users waiting for the results of a long running query.
Lesson Summary
You now have all the information required to send Cypher queries to Neo4j and consume the results.
Next, we will look at the Cypher Type System and some of the considerations that you need to make when working with values coming from Neo4j in your Java application.