You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data Neo4j自定义查询返回ScoredSnippet复合对象报错解决

问题描述

在spring-data-neo4j(7.3.0)环境中使用Neo4J向量相似度搜索时,尝试返回包含实体和相似度得分的复合对象时遇到错误。

现有代码

复合返回对象定义

public record ScoredSnippet(Snippet snippet, Double score)

仓库接口

public interface SnippetRAGRepository extends ReactiveNeo4jRepository<ScoredSnippet, String>
{
    @Query("""
       MATCH (s:Snippet)
       WITH s, vector.similarity.cosine(s.embedding, toFloatList($embedding)) AS score
       RETURN s, score
       ORDER BY score DESC LIMIT $limit
       """)
    public Flux<ScoredSnippet> findBySemantics(double [] embedding, int limit );
}

错误信息

当返回类型为Snippet时运行正常,但返回ScoredSnippet时抛出以下异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'snippetRAGRepository' defined in ai.amber.core.snippets.SnippetRAGRepository defined in @EnableReactiveNeo4jRepositories declared on Neo4jReactiveRepositoriesRegistrar.EnableReactiveNeo4jRepositoriesConfiguration: Required identifier property not found for class ai.amber.core.snippets.ScoredSnippet
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1788) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:969) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962) ~[spring-context-6.1.8.jar:6.1.8]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.8.jar:6.1.8]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.3.0-SNAPSHOT.jar:3.3.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.0-SNAPSHOT.jar:3.3.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.0-SNAPSHOT.jar:3.3.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.0-SNAPSHOT.jar:3.3.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.0-SNAPSHOT.jar:3.3.0-SNAPSHOT]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.0-SNAPSHOT.jar:3.3.0-SNAPSHOT]
    at ai.amber.core.Main.main(Main.java:15) ~[main/:na]
Caused by: java.lang.IllegalStateException: Required identifier property not found for class ai.amber.core.snippets.ScoredSnippet
    at org.springframework.data.mapping.PersistentEntity.getRequiredIdProperty(PersistentEntity.java:135) ~[spring-data-commons-3.3.0.jar:3.3.0]
    at org.springframework.data.repository.core.support.PersistentEntityInformation.getIdType(PersistentEntityInformation.java:58) ~[spring-data-commons-3.3.0.jar:3.3.0]
    at org.springframework.data.neo4j.repository.support.ReactiveNeo4jRepositoryFactory.getTargetRepository(ReactiveNeo4jRepositoryFactory.java:76) ~[spring-data-neo4j-7.3.0.jar:7.3.0]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:317) ~[spring-data-commons-3.3.0.jar:3.3.0]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:286) ~[spring-data-commons-3.3.0.jar:3.3.0]
    at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) ~[spring-data-commons-3.3.0.jar:3.3.0]
    at org.springframework.data.util.Lazy.get(Lazy.java:113) ~[spring-data-commons-3.3.0.jar:3.3.0]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:292) ~[spring-data-commons-3.3.0.jar:3.3.0]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1835) ~[spring-beans-6.1.8.jar:6.1.8]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1784) ~[spring-beans-6.1.8.jar:6.1.8]
    ... 16 common frames omitted

用户疑问:原以为用投影可以提取这类复合对象,无需添加完整的@Node注解框架。尝试过将ScoredSnippet作为接口、记录或普通DTO类使用,请问正确的实现方式是什么?


解决方案

问题根源是你让仓库继承ReactiveNeo4jRepository<ScoredSnippet, String>时,Spring Data会把ScoredSnippet当作需要持久化的实体类,强制要求它有标识属性(ID),但ScoredSnippet只是封装查询结果的DTO,并非Neo4j节点实体。

正确实现方式有两种:

方式一:仓库泛型使用实际节点实体

让仓库继承ReactiveNeo4jRepository<Snippet, String>(Snippet是带@Node注解的实际节点实体),自定义方法仍返回Flux<ScoredSnippet>:

public interface SnippetRAGRepository extends ReactiveNeo4jRepository<Snippet, String>
{
    @Query("""
       MATCH (s:Snippet)
       WITH s, vector.similarity.cosine(s.embedding, toFloatList($embedding)) AS score
       RETURN s, score
       ORDER BY score DESC LIMIT $limit
       """)
    Flux<ScoredSnippet> findBySemantics(double [] embedding, int limit );
}

ScoredSnippet保持记录类即可,无需额外注解,Spring Data Neo4j会自动根据属性名匹配查询结果(查询返回的s对应snippet参数,score对应score参数)完成映射。

方式二:使用极简仓库接口

如果该仓库不需要CRUD操作,仅需自定义查询,可以让仓库继承Repository<Snippet, String>或ReactiveQueryByExampleExecutor<Snippet>:

public interface SnippetRAGRepository extends Repository<Snippet, String>
{
    @Query("""
       MATCH (s:Snippet)
       WITH s, vector.similarity.cosine(s.embedding, toFloatList($embedding)) AS score
       RETURN s, score
       ORDER BY score DESC LIMIT $limit
       """)
    Flux<ScoredSnippet> findBySemantics(double [] embedding, int limit );
}

这种方式下Spring Data不会将仓库与DTO绑定为实体仓库,仅处理自定义查询的结果映射。

补充说明

  • ScoredSnippet也可以用投影接口实现,只要方法名与查询返回字段对应即可:
public interface ScoredSnippet {
    Snippet getSnippet();
    Double getScore();
}
  • 核心原则:仓库的泛型参数必须是实际的Neo4j节点实体,不能是DTO类,否则Spring Data会尝试将DTO当作实体处理,触发找不到ID属性的错误。

内容的提问来源于stack exchange,提问作者Fima Rotter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 19:09:50