简体   繁体   中英

Custom query projections with Spring Data neo4j: Two related nodes including the relationship

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 (ie 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.

This is something SDN cannot do out-of-the-box because this outer view on a wrapper of domain entities does not fit to the entity-centric approach.

However, you can use the Neo4jClient to manually query the data and use the already existing mapper for your node entity class.

BiFunction<TypeSystem, MapAccessor, Event> mappingFunction = neo4jMappingContext.getRequiredMappingFunctionFor(Event.class);

client.query("MATCH (evt1:Event)-[possibleDupe:POSSIBLE_DUPE]->(evt2:Event) " +
        "RETURN {evt1: evt1, possibleDupe: possibleDupe, evt2: evt2} AS duplicateEntry" +
        "ORDER BY possibleDupe.differenceScore")
        .fetchAs(DuplicateEntry.class)
        .mappedBy((typeSystem, record) -> {
            var duplicateEntry = (Map<String, Object>) record.get("duplicateEntry");
            var event1 = mappingFunction.apply(typeSystem, duplicateEntry.get("evt1"));
            var event2 = mappingFunction.apply(typeSystem, duplicateEntry.get("evt2"));
            var possibleDupe = //... derive something from duplicateEntry.get("possibleDupe")
            return new DuplicateEntry(event1, possibleDupe, event2);
        }).all();

(just written down, might contain some typos)

More can be found here: https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#neo4j-client.result-objects.mapping-functions

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();

    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM