在 Spring Data Neo4j 中将域对象作为参数传递给自定义 Cypher 查询

Passing domain object as argument to custom Cypher query in Spring Data Neo4j

提问人:Podbrushkin 提问时间:11/8/2023 最后编辑:Podbrushkin 更新时间:11/20/2023 访问量:56

问:

我正在编写Spring Data Neo4j webapp。 我有这个域对象:

@Node("Person")
public class Person {
    
    @Id
    @GeneratedValue
    private String id;

和一段关系。(:Image) - [:DEPICTS] -> (:Person)

我需要计算描绘特定人物的图像。 这是我的尝试:

public interface ImageRepositoryNeo4j extends Neo4jRepository<Image, String> {
@Query("""
        MATCH (i:Image) -[:DEPICTS] -> (p:Person)
        WHERE p = $person
        RETURN count(i)
        """)
    // DSNT WORK
    public Integer countByPeopleContains(@Param("person") Person person);

但它总是返回 0。

其他失败的尝试

@Query("""
        MATCH (i:Image)-[:DEPICTS]->(p:Person)
        WHERE p IN $people
        RETURN count(i)
        """)
    // DOESN'T WORK, 0 RESULTS
    public Integer countByPeopleContains(@Param("people") Collection<Person> people);

@Query("""
        MATCH (i:Image) -[:DEPICTS] -> (p:Person)
        WHERE ID(p) = $person.__id__
        RETURN count(i)
        """)
    // DSNT WORK
    public Integer countByPeopleContains(@Param("person") Person person);

@Query("""
        MATCH (i:Image) -[:DEPICTS] -> (p:Person)
        WHERE id(p)=$person
        RETURN count(i)
        """)
    // DSNT WORK
    public Integer countByPeopleContains(@Param("person") String personId);

@Query("""
        MATCH (i:Image) -[:DEPICTS] -> (p:Person {id: $person.__id__})
        RETURN count(i)
        """)
    // DSNT WORK, ALWAYS 0
    public Integer countByPeopleContains(@Param("person") Person person);

它们都返回 0。

这个有效,但由于不是唯一的属性,它不适合域逻辑:fullName

@Query("""
        MATCH (i:Image) -[:DEPICTS] -> (p:Person {fullName: $person})
        RETURN count(i)
        """)
    // TODO: personFullname is not unique, use smth else
    public Integer countByPeopleContains(@Param("person") String personFullName);

如何在Spring Data Neo4j存储库的自定义Cypher查询中使用域对象?

线索

Spring Data Neo4j文档中,我发现最接近我需要的是:

@Node
public final class Movie {

    @Id
    private final String title;
...
interface MovieRepository extends Neo4jRepository<Movie, String> {

    @Query("MATCH (m:Movie {title: $movie.__id__})\n"
           + "MATCH (m) <- [r:DIRECTED|REVIEWED|ACTED_IN] - (p:Person)\n"
           + "return m, collect(r), collect(p)")
    Movie findByMovie(@Param("movie") Movie movie);
}

但出于某种原因,它在我的情况下不起作用。也许那是因为我的 id 是 .@GeneratedValue

另一条线索

在 Neo4j 嵌入式文档中,有一个关于如何在没有 Spring Data Neo4j 的情况下将对象传递给 Cypher 的示例:

Map<String,Object> params = new HashMap<>();
params.put( "node", bobNode );

String query =
    "MATCH (n:Person)" + "\n" +
    "WHERE n = $node" + "\n" +
    "RETURN n.name";

Result result = transaction.execute( query, params );

它的工作原理是$node:org.neo4j.graphdb.Node

@Bean
CommandLineRunner countForGregWithoutSDN(GraphDatabaseService graphDatabaseService) {
    return args -> {
        try (var tx = graphDatabaseService.beginTx()) {
            String cypherGetGreg = "MATCH (n:Person {birthday: date('1993-03-03')}) RETURN n LIMIT 5;";
            Result result = tx.execute(cypherGetGreg);
            var greg1993 = (Node) result.next().get("n");
            if (!greg1993.getProperty("name").equals("Greg")) 
                throw new RuntimeException("Should be Greg1993 but actually "+greg1993);
            result.close();

            Map<String,Object> params = new HashMap<>();
            params.put( "node", greg1993 );

            String cypherCountForGreg = """
                MATCH (i:Image) -[:DEPICTS] -> (p:Person)
                WHERE p = $node
                RETURN count(i)
                """;
            result = tx.execute( cypherCountForGreg, params );
            var count = result.next().values().iterator().next();
            System.out.println("Count for Greg1993 is "+count); // 1
            result.close();
        }
    };
}

但如果 $node 是 SDN 实体,则结果为 0。

java neo4j spring-data cypher spring-data-neo4j

评论

0赞 Vincent Rupp 11/14/2023
是节点还是属性?$person
0赞 Podbrushkin 11/14/2023
@Vincent 它是一个节点。

答:

0赞 Vincent Rupp 11/15/2023 #1

我怀疑问题出在评估节点相等性上。

而不是尝试WHERE p = $personWHERE id(p) = id($person)

更新:根据这篇文章,我可能会弄错:应该直接比较节点还是必须首先通过 ID()?

评论

0赞 Podbrushkin 11/15/2023
我试过了,当调用此方法时,会抛出此运行时异常:org.neo4j.driver.exceptions.ClientException: Invalid input for function 'id()': Expected Map{__labels__ -> List{String("Person")}, __id__ -> String("4:21f6f6f2-ea32-4b8c-a8a7-e953f6f436c2:1559"), __properties__ -> Map{birthday -> NO_VALUE, dotId -> String("somedotid"), fullName -> String("vasya pupkin")}} to be a node or relationship, but it was 'Map'
0赞 Vincent Rupp 11/15/2023
$person那么就不是一个节点,不是 Neo4j 需要它的方式。是否必须传入 Person 节点对象,或者是否可以将 PersonID 作为节点上的索引属性?
0赞 Podbrushkin 11/15/2023
它是一个节点。它是用 注释的类的对象,它是从 neo4jRepository 返回的,正如您在异常 SDN added 和 properties 中看到的那样,后者是 neo4j 生成的。如果需要,我可以提供 MRE。我还没有找到任何关于我在任何地方尝试做什么的工作示例。我可以更改此方法的签名并传递字符串,但它也不起作用(请参阅“失败的尝试”),并且 func 已被弃用,neo4j 团队不鼓励使用内部节点 ID。@Node__labels____id__person.idid()
0赞 Vincent Rupp 11/15/2023
你是对的.至于类,错误说整个东西是一个映射,而不是一个节点或关系,我不确定这是如何发生的。你可以试试或.否则,MRE将非常有用。id()p = apoc.convert.toNode($person)apoc.create.vNode($person)
0赞 Podbrushkin 11/15/2023
给你,MRE。我希望重新创建目录结构不会花费太多时间,通常我会像这样在 pwsh 中这样做:等等。使用 运行它。感谢您的关注。如果我们可以在不依赖 id 的情况下在自定义密码中引用我们的实体对象,那就太好了。但是现在我甚至无法通过id做到这一点。gcb -Raw | New-Item -Force '.\src\main\java\myapp\repo\PersonRepositoryNeo4j.java'mvn clean spring-boot:run -e