Spring Boot中无法将Neo4j路径查询结果映射到Kotlin数据类
我在Neo4j中查询两个节点间的所有路径,节点和关系创建正常,但将包含节点和关系的路径结果映射到Kotlin数据类时出错。
仓库层代码及查询
import com.nextbus.nextbusapi.DTO.RelationshipDTO import com.nextbus.nextbusapi.entity.MyNode import org.springframework.data.neo4j.repository.Neo4jRepository import org.springframework.data.neo4j.repository.query.Query import org.springframework.data.repository.query.Param interface MyNodeRepository : Neo4jRepository<MyNode, Long>{ fun findByName(name:String): MyNode? @Query( """MATCH p = ( :MyNode {name: 'istanbul'})-[*]-( :MyNode {name: 'ankara'}) WITH relationships(p) AS rels RETURN collect(rels)""", ) fun findPathsBetweenNodes( @Param("startNodeName") startNodeName: String, @Param("endNodeName") endNodeName: String ): List<List<RelationshipDTO>> }
定义的RelationshipDTO
data class RelationshipDTO( val identity: Long, val start: Long, val end: Long, val type: String, val properties: MutableMap<String, String>, val elementId: String, val startNodeElementId: String, val endNodeElementId: String )
报错信息
2024-02-14T16:10:27.669+03:00 ERROR 1884 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessApiUsageException: LIST OF ANY? is not a keyed collection; Error code 'N/A'] with root cause
org.neo4j.driver.exceptions.value.NotMultiValued: LIST OF ANY? is not a keyed collection
尝试将返回类型改为List<Any>后,接口返回异常数据:
[
{
"empty": false,
"false": false,
"true": false,
"null": false
}
]
解决方案
1. 修复Cypher查询的参数与返回结构问题
原查询存在两个关键问题:
- 硬编码节点名称,未使用方法传入的
startNodeName和endNodeName参数 - 使用
collect(rels)将所有路径的关系合并为一个大集合,破坏了"每个路径对应一个关系列表"的结构
修改后的Cypher查询:
MATCH p = (start:MyNode {name: $startNodeName})-[*]-(end:MyNode {name: $endNodeName}) RETURN relationships(p) AS pathRels
对应的仓库方法:
@Query( """ MATCH p = (start:MyNode {name: $startNodeName})-[*]-(end:MyNode {name: $endNodeName}) RETURN relationships(p) AS pathRels """, ) fun findPathsBetweenNodes( @Param("startNodeName") startNodeName: String, @Param("endNodeName") endNodeName: String ): List<List<RelationshipDTO>>
2. 调整DTO字段适配Neo4j Driver返回结构
Neo4j Driver返回的关系对象字段与你定义的DTO存在映射不匹配问题,调整后的DTO需对应Driver的关系属性:
data class RelationshipDTO( val identity: Long, // 对应关系的数值ID val elementId: String, // 对应关系的字符串ID(Neo4j 4+推荐) val start: Long, // 对应起始节点数值ID val startNodeElementId: String, // 对应起始节点字符串ID val end: Long, // 对应结束节点数值ID val endNodeElementId: String, // 对应结束节点字符串ID val type: String, // 关系类型 val properties: Map<String, String> // 关系属性,若属性非全字符串可改为Map<String, Any> )
3. 手动映射(更可靠的方案)
如果Spring Data自动映射仍有问题,可直接返回Neo4j Driver的Path对象,在服务层手动转换为DTO:
仓库层修改
@Query( """ MATCH p = (start:MyNode {name: $startNodeName})-[*]-(end:MyNode {name: $endNodeName}) RETURN p """, ) fun findPaths( @Param("startNodeName") startNodeName: String, @Param("endNodeName") endNodeName: String ): List<org.neo4j.driver.types.Path>
服务层转换逻辑
fun getPaths(startNodeName: String, endNodeName: String): List<List<RelationshipDTO>> { val paths = myNodeRepository.findPaths(startNodeName, endNodeName) return paths.map { path -> path.relationships().map { rel -> RelationshipDTO( identity = rel.id(), elementId = rel.elementId(), start = rel.startNodeId(), startNodeElementId = rel.startNodeElementId(), end = rel.endNodeId(), endNodeElementId = rel.endNodeElementId(), type = rel.type(), properties = rel.properties().mapValues { it.value.asString() } ) } } }
内容的提问来源于stack exchange,提问作者Emre Tüker

