spring-data-neo4j v6:找不到能够从类型 [MyDTO] 转换为类型 [org.neo4j.driver.Value] 的转换器

spring-data-neo4j v6: No converter found capable of converting from type [MyDTO] to type [org.neo4j.driver.Value]

情况

我正在将 kotlin spring 数据 neo4j 应用程序从 spring-data-neo4j 版本 5.2.0.RELEASE 迁移到版本 6.0.11

原始应用程序有几个带有自定义查询的存储库接口,这些接口将一些 DTO 作为参数,并使用各种 DTO 字段来构造查询。所有这些类型的查询目前都失败了

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [MyDTO] to type [org.neo4j.driver.Value]

reference documentation for spring-data-neo4j v6 仅提供示例,其中传递给 @Repository 接口的自定义查询方法的参数与与该存储库关联的 @Node class 的类型相同.文档没有明确说明只允许节点 class 的参数。

问题

有没有办法将任意 DTO(不是 @Node class)传递给 spring-data 中 @Repository 接口中的自定义查询方法-neo4j v6 就像在 v5 中一样?

代码示例

示例节点实体

@Node
data class MyEntity(
    @Id
    val attr1: String,
    val attr2: String,
    val attr3: String
)

示例 DTO

data class MyDTO(
    val field1: String,
    val field2: String
)

存储库界面示例

@Repository
interface MyRepository : PagingAndSortingRepository<MyEntity, String> {

    // ConverterNotFoundException is thrown when this method is called
    @Query("MATCH (e:MyEntity {attr1: {0}.field1}) " +
           "CREATE (e)-[l:LINK]->(n:OtherEntity {attr2: {0}.field2))")
    fun doSomethingWithDto(dto: MyDTO)
}

到目前为止尝试过的解决方案

将 DTO 注释为节点实体

基于参考文档中的以下内容 https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#custom-queries.parameters

Mapped entities (everything with a @Node) passed as parameter to a function that is annotated with a custom query will be turned into a nested map.

@Node
data class MyDTO(
    @Id
    val field1: String,
    val field2: String
)

在自定义查询中将 {0} 替换为 [=26=]

基于参考文档中的以下内容 https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#custom-queries.parameters

You do this exactly the same way as in a standard Cypher query issued in the Neo4j Browser or the Cypher-Shell, with the $ syntax (from Neo4j 4.0 on upwards, the old {foo} syntax for Cypher parameters has been removed from the database).

...

[In the given listing] we are referring to the parameter by its name. You can also use [=37=] etc. instead.

@Repository
interface MyRepository : PagingAndSortingRepository<MyEntity, String> {
    
    // ConverterNotFoundException is thrown when this method is called
    @Query("MATCH (e:MyEntity {attr1: [=17=].field1}) " +
           "CREATE (e)-[l:LINK]->(n:OtherEntity {attr2: [=17=].field2))")
    fun doSomethingWithDto(dto: MyDTO)
}

详情

RTFM Custom conversions ...

我自己找到了解决方案。希望其他人也能从中受益。

解决方案

创建自定义转换器

import mypackage.model.*
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.neo4j.driver.Value
import org.neo4j.driver.Values
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
import java.util.HashSet

class DtoToNeo4jValueConverter : GenericConverter {
    override fun getConvertibleTypes(): Set<ConvertiblePair>? {
        val convertiblePairs: MutableSet<ConvertiblePair> = HashSet()
        convertiblePairs.add(ConvertiblePair(MyDTO::class.java, Value::class.java))
        return convertiblePairs
    }

    override fun convert(source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor?): Any? {
        return if (MyDTO::class.java.isAssignableFrom(sourceType.type)) {
            // generic way of converting an object into a map
            val dataclassAsMap = jacksonObjectMapper().convertValue(source as MyDTO, object :
                    TypeReference<Map<String, Any>>() {})
            Values.value(dataclassAsMap)
        } else null
    }
}

在配置中注册自定义转换器

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.neo4j.core.convert.Neo4jConversions
import org.springframework.core.convert.converter.GenericConverter
import java.util.*


@Configuration
class MyNeo4jConfig {
    @Bean
    override fun neo4jConversions(): Neo4jConversions? {
        val additionalConverters: Set<GenericConverter?> = Collections.singleton(DtoToNeo4jValueConverter())
        return Neo4jConversions(additionalConverters)
    }
}

荒谬的是框架会强迫你为此编写一个自定义转换器。我在覆盖的用户 class 中为一组有限的 update-able 用户配置文件字段创建了一个 @Transient 对象,但我遇到了同样的错误。我想我只需要在方法参数中将对象分解成它的组件字符串字段就可以解决这个问题。真是一团糟。

@Query("MATCH (u:User) WHERE u.username = :#{#username} SET u.firstName = :#{#up.firstName},u.lastName = :#{#up.firstName},u.intro = :#{#up.intro} RETURN u")
Mono<User> update(@Param("username") String username,@Param("up") UserProfile up);

未找到能够从类型 [...UserProfile] 转换为类型 [org.neo4j.driver.Value] 的转换器