About

The project itself is not an official Neo4j library.

This project can be seen as a companion library for the official Neo4j Java Driver. It does not aim to replace or wrap functionality of the driver, but instead enriches already existing mechanisms.

The core idea (from today’s perspective) is to take some concepts and learnings of object-graph-mapping libraries like Neo4j-OGM or Spring Data Neo4j and re-assemble them into light-weight modules.

Namely, those parts are currently:

  • Read-only mapper

    Converts Java driver’s records into Java objects.

  • Parameter renderer

    Serializes a given Java object into a Java driver Value map.

Feel free to provide feedback and ideas or in general contributions at https://github.com/meistermeier/neo4j-java-toolbelt.

Dependencies

The mapper and renderer are currently part of the very same artifact: neo4j-java-toolbelt-mapper. This might change in the future or the combined artifact will get renamed.

Listing 1. Maven dependency
<dependency>
    <groupId>com.meistermeier.neo4j.toolbelt</groupId>
    <artifactId>neo4j-java-toolbelt-mapper</artifactId>
    <version>0.1.0</version>
</dependency>

Mapper

The mapper is meant for converting Neo4j Java driver’s Record into Java objects. It generates a typed Function<Record, T> that can be provided to the driver’s Result#list method.

The supported return patterns are:

  • Node(s)

  • Values

  • Map structure

Because the library wants to avoid any effect on the application code, there are no annotations or other kind meta-information. This leads to the question: What does get mapped in the end? The answer to this is pretty simple. The node’s properties, values names or keys in the map structure have to match the names of the defined parameter in the constructor of the target record/class.

Also, the mapper does not take care about post-initialization population of properties. There is only one mapping phase per instance and this is the instantiation via the best-matching constructor (type and name wise).

If you are working with classes, you need to compile your application with -parameters to preserve the constructor’s parameter names. Otherwise, the mapper cannot link the right values.

There is also support to map related nodes. To have an unambiguous definition, the result has to have the format RETURN n, [relatedNode] as relatedNodes where relatedNodes refers to the name of a List<RelatedNodeType> relatedNodes defined field.

Example

To get started, a node returned from the driver, should get mapped into a record with two fields.

Listing 2. Record for mapping
public record Person(String name, Integer yearBorn) {}

To retrieve the mapping function, the Mapper class provides a createMapper method. Called with the matching type, it generates the right mapping function.

Listing 3. Generate mapping function
Function<Record, Person> personConverter = Mapper.INSTANCE.createMapperFor(Person.class);

This mapping function can be used in the Result#list function to map the individual Records. This does, of course, also work for single results with a list size of 1.

Listing 4. Use mapping function in list function
List<Person> people = session.run("MATCH (p:Person) return p")
		.list(personConverter);

Since this is a Java standard Function, it can also get applied to the Result#single returning Record.

Listing 5. Apply mapping function to driver Record
Record singleNode = session.run("MATCH (p:Person) return p").single();
Person person = personConverter.apply(singleNode);

Parameter Renderer

With a Renderer, it is possible to render a class into a map of driver values. Those parameters can then be used within the driver’s Session#query method.

Listing 6. Parameter providing record
record ParameterRecord(String a, int b) {}

A populated instance of this type can then be handed over to the Renderer to get mapped into a driver Value. In detail, it will become a MapValue in this case.

Listing 7. Parameter record rendering
ParameterRecord parameterRecord = new ParameterRecord("test", 4711);
Value parameters = Renderer.INSTANCE.toParameters(parameterRecord);
Record result = session.run("RETURN $a, $b", parameters).single();
System.out.println(result); // Record<{$a: "test", $b: 4711}>

The Renderer can also produce Cypher collections of rendered instances (for driver’s experts: ListValue). By default, those collections are named rows.

Listing 8. Collection parameter rendering
ParameterRecord parameterRecord1 = new ParameterRecord("test1", 4711);
ParameterRecord parameterRecord2 = new ParameterRecord("test2", 42);
Value parameters = Renderer.INSTANCE.toParameters(List.of(parameterRecord1, parameterRecord2));
session.run("UNWIND $rows as row RETURN row.a, row.b", parameters).forEachRemaining(
		System.out::println
);
// Record<{row.a: "test1", row.b: 4711}>
// Record<{row.a: "test2", row.b: 42}>

In the example above the Renderer#toParameters(Collection) method is a shortcut for Renderer#toParameters(Collection, String). It is possible to use the overloaded method directly and name the collection that gets rendered explicitly.

Listing 9. Named collection parameter rendering
ParameterRecord parameterRecord1 = new ParameterRecord("test1", 4711);
ParameterRecord parameterRecord2 = new ParameterRecord("test2", 42);
Value parameters = Renderer.INSTANCE.toParameters(List.of(parameterRecord1, parameterRecord2), "things");
session.run("UNWIND $things as thing RETURN thing.a, thing.b", parameters).forEachRemaining(
		System.out::println
);
// Record<{thing.a: "test1", thing.b: 4711}>
// Record<{thing.a: "test2", thing.b: 42}>

Supported types

Although these are the supported types for the Mapper, the Renderer might not support some of them yet.

Java Cypher/Driver

Long

Long

Integer

Long

Double

Double

Float

Double

String

String

Boolean

Boolean

LocalDate

LocalDate

LocalDateTime

LocalDateTime

LocalTime

LocalTime

OffsetDateTime

OffsetDateTime

OffsetTime

OffsetTime

ZonedDateTime

ZonedDateTime

List<> of everything ^^

[] of everything ^^

List<RelatedNode>

[] of nodes