I'm struggling to understand how projections work in Spring Data neo4j or more precisely what can be done with them and what are the limitations (i.e. when to use other forms of mapping). Is there some documentation or examples for this topic?
Here is the concrete problem I'm having at the moment: I have nodes linked by a relationship that represents their similarity. I want to list them ordered by relationship with as few queries as possible, of course.
Here is one way to query that
MATCH (evt1:Event)-[possibleDupe:POSSIBLE_DUPE]->(evt2:Event)
RETURN {evt1: evt1, possibleDupe: possibleDupe, evt2: evt2} AS duplicateEntry
ORDER BY possibleDupe.differenceScore
I thought I could just project that to a DTO:
public class DuplicateEntry {
public DuplicateEntry(Event evt1, PossibleDupe possibleDupe, Event evt2) {
this.evt1 = evt1;
this.possibleDupe = possibleDupe;
this.evt2 = evt2;
}
@Getter @Setter
private Event evt1;
@Getter @Setter
private PossibleDupe possibleDupe;
@Getter @Setter
private Event evt2;
}
The usual way would be to do something like this:
Statement statement = CypherParser.parse("MATCH (evt1:Event)-[possibleDupe:POSSIBLE_DUPE]->(evt2:Event) RETURN evt1, possibleDupe, evt2");
repository.findAll(statement, Event.class);
But that can not be mapped in that way, because both sides of the relation have the same type, so the mapper does not know which is which: More than one matching node in the record.
meistermeier led me on the right path. Here is the final code.
The DTO:
public class DuplicateEntry {
public DuplicateEntry(Event evt1, int differenceScore, Event evt2) {
this.evt1 = evt1;
this.differenceScore = differenceScore;
this.evt2 = evt2;
}
@Getter @Setter
private Event evt1;
@Getter @Setter
private int differenceScore;
@Getter @Setter
private Event evt2;
}
The code in the service class for querying and mapping (I added pagination also):
public Collection<DuplicateEntry> getPossibleDupes(int page, int size) {
BiFunction<TypeSystem, MapAccessor, Event> mappingFunction =
neo4jMappingContext.getRequiredMappingFunctionFor(Event.class);
return neo4jClient.query("""
MATCH (evt1:Event)-[possibleDupe:POSSIBLE_DUPE]->(evt2:Event)
RETURN {evt1: evt1, possibleDupe: possibleDupe, evt2: evt2} AS duplicateEntry
ORDER BY possibleDupe.differenceScore
SKIP $skip LIMIT $size
""")
.bind(size).to("size")
.bind(page * size).to("skip")
.fetchAs(DuplicateEntry.class)
.mappedBy((typeSystem, record) -> {
MapValue duplicateEntry = (MapValue) record.get("duplicateEntry");
var event1 = mappingFunction.apply(typeSystem, duplicateEntry.get("evt1"));
var event2 = mappingFunction.apply(typeSystem, duplicateEntry.get("evt2"));
return new DuplicateEntry(
event1,
duplicateEntry.get("possibleDupe")
.get("differenceScore").asInt(),
event2);
}).all();
}