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

Spring Boot升级后@CompositeProperty嵌套Map存储Neo4j报错咨询

Spring Boot 2.7升级后Neo4j @CompositeProperty嵌套Map存储问题解决方案

当你把Spring Boot从2.3.12升级到2.7.x,将Neo4j节点的@Properties替换为@CompositeProperty后,嵌套Map无法像之前那样自动展开存储,这是因为Spring Data Neo4j(SDN)6.x(对应Spring Boot 2.7)对@CompositeProperty的实现逻辑做了调整:

  • 旧版@Properties会递归遍历嵌套Map,将其展开为扁平的前缀式键值对(比如params.key2.mapKey1)存储。
  • 新版@CompositeProperty默认仅支持单层简单类型的Map,嵌套的Map会被当作整体对象处理,但Neo4j本身不支持将Map作为属性值存储,因此抛出Neo.ClientError.Statement.TypeError错误。

要兼容原有数据格式且无需迁移,你可以通过自定义属性转换器来恢复旧版的嵌套Map展开逻辑:

步骤1:实现嵌套Map的转换逻辑

创建一个自定义转换器,手动处理嵌套Map的扁平化与还原:

import org.springframework.data.neo4j.core.convert.Neo4jConversionService
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty

class NestedMapCompositeConverter : Neo4jPersistentPropertyConverter<MutableMap<String, Any?>> {

    // 将嵌套Map扁平化为前缀式键值对,写入Neo4j
    override fun write(source: MutableMap<String, Any?>, sink: Neo4jConversionService.PropertyConverterSink) {
        flattenNestedMap(source, "params", sink)
    }

    // 从扁平的前缀式键值对还原为嵌套Map
    override fun read(source: Map<String, Any>, property: Neo4jPersistentProperty): MutableMap<String, Any?> {
        return unflattenToNestedMap(source, "params")
    }

    private fun flattenNestedMap(map: Map<String, Any?>, prefix: String, sink: Neo4jConversionService.PropertyConverterSink) {
        map.forEach { (key, value) ->
            val fullKey = "$prefix.$key"
            when (value) {
                is Map<*, *> -> flattenNestedMap(value as Map<String, Any?>, fullKey, sink)
                else -> sink.write(fullKey, value)
            }
        }
    }

    private fun unflattenToNestedMap(source: Map<String, Any>, prefix: String): MutableMap<String, Any?> {
        val result = mutableMapOf<String, Any?>()
        val prefixWithDot = "$prefix."

        source.forEach { (key, value) ->
            if (key.startsWith(prefixWithDot)) {
                val remainingKey = key.substring(prefixWithDot.length)
                val splitIndex = remainingKey.indexOf('.')

                if (splitIndex == -1) {
                    // 无嵌套的键直接存入
                    result[remainingKey] = value
                } else {
                    // 有嵌套的键,逐层构建Map
                    val topLevelKey = remainingKey.substring(0, splitIndex)
                    val nestedKey = remainingKey.substring(splitIndex + 1)

                    val nestedMap = result.getOrDefault(topLevelKey, mutableMapOf<String, Any?>()) as MutableMap<String, Any?>
                    nestedMap[nestedKey] = value
                    result[topLevelKey] = nestedMap
                }
            }
        }
        return result
    }
}

步骤2:注册转换器到Neo4j配置

创建Neo4j配置类,将自定义转换器注册到转换服务中:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.neo4j.core.convert.Neo4jConversions

@Configuration
class CustomNeo4jConfig {

    @Bean
    fun neo4jConversions(): Neo4jConversions {
        val converters = mutableSetOf<Any>()
        converters.add(NestedMapCompositeConverter())
        return Neo4jConversions(converters)
    }
}

步骤3:关联转换器与属性

在你的Neo4j节点类中,为params属性指定自定义转换器:

@CompositeProperty(prefix = "params", converter = NestedMapCompositeConverter::class)
var params: MutableMap<String, Any?> = mutableMapOf()

完成以上配置后,@CompositeProperty就会像旧版@Properties一样处理嵌套Map,自动展开存储为扁平的前缀式键值对,完全兼容原有数据,无需迁移。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 06:43:13