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

使用带Statement参数的ReactiveNeo4jTemplate findAll方法返回空结果

问题:ReactiveNeo4jTemplate带Statement参数的findAll方法返回空结果,导致updateAll失效

使用ReactiveNeo4jTemplate的findAll方法并传入Statement参数时,数据库中存在匹配该Statement的节点,但方法返回空结果。其他模板方法均可正常工作,仅此方法异常。

测试用例需要更新符合特定条件的所有元素,但由于带Statement的findAll无返回值,更新逻辑未执行;而不带查询条件的findAll方法可正常获取所有元素。


相关代码实现

Neo4jQueryableRepositoryAdapter类

class Neo4jQueryableRepositoryAdapter<T : Any, ID : Any>(
    private val delegator: Neo4jRepository<T, ID>,
    clazz: KClass<T>
) : QueryableRepository<T, ID>, Repository<T, ID> by delegator {
    private val parser = Neo4jCriteriaParser(clazz)
 
// 更多代码和函数

override fun updateAll(
        criteria: Criteria,
        patch: Patch<T>,
        limit: Int?,
        offset: Long?,
        sort: Sort?
    ): Flow<T> {
        return parser.parse(criteria).let { delegator.updateAll(it, patch, limit, offset, sort) }
    }

// 更多代码和函数

}

SimpleNeo4jRepository类

@Suppress("UNCHECKED_CAST")
class SimpleNeo4jRepository<T : Any, ID : Any>(
    private var template: ReactiveNeo4jTemplate,
    val clazz: KClass<T>,
    eventPublisher: EventPublisher? = null,
) : Neo4jRepository<T, ID> {

// 更多代码和函数

override fun updateAll(
        criteria: Statement,
        patch: Patch<T>,
        limit: Int?,
        offset: Long?,
        sort: Sort?
    ): Flow<T> {
        return updateAll(criteria, patch.async(), limit, offset, sort)
    }

override fun updateAll(
        criteria: Statement,
        patch: SuspendPatch<T>,
        limit: Int?,
        offset: Long?,
        sort: Sort?
    ): Flow<T> {
        return findAll(criteria, limit, offset, sort)
            .map { update(it, patch) }
            .filterNotNull()
    }

override fun findAll(criteria: Statement?, limit: Int?, offset: Long?, sort: Sort?): Flow<T> {
            // 验证limit和offset
            val l = limit ?: Int.MAX_VALUE
            val o = offset ?: 0

            // 若指定条件则执行自定义查询
            val all = if (criteria != null) {
                println("QUERY ➡️ ${criteria.cypher}")
                template.findAll(criteria, clazz.java)
            } else {
                template.findAll(clazz.java)
            }.subscribeOn(Schedulers.parallel())
                .asFlow()

            // 应用limit和offset并返回结果
            return all.drop(o.toInt()).take(l)
    }

// 更多代码和函数

}

Neo4j配置类

import com.astrum.data.annotation.ConverterScope
import org.neo4j.driver.AuthTokens
import org.neo4j.driver.Driver
import org.neo4j.driver.GraphDatabase
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.core.env.Environment
import org.springframework.data.convert.ReadingConverter
import org.springframework.data.convert.WritingConverter
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig
import org.springframework.data.neo4j.core.convert.Neo4jConversions
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories
import org.springframework.transaction.annotation.EnableTransactionManagement

@Configuration
@EnableReactiveNeo4jRepositories("com.astrum")
@EnableTransactionManagement
class Neo4jConfiguration(
    private val applicationContext: ApplicationContext,
    private val env: Environment
) : AbstractReactiveNeo4jConfig() {

    @Bean
    override fun neo4jConversions(): Neo4jConversions {
        val converters = applicationContext.getBeansOfType(GenericConverter::class.java)
            .values
            .filter {
                it.javaClass.annotations.any { annotation ->
                    annotation is WritingConverter || annotation is ReadingConverter
                }
            }
            .filter {
                val scope = it.javaClass.annotations.filterIsInstance<ConverterScope>()
                scope.isEmpty() || scope.any { converterScope -> converterScope.type == ConverterScope.Type.NEO4J }
            }
        return Neo4jConversions(converters)
    }
    /**
     * 用于与Neo4j交互的驱动。
     *
     * @return 要使用的Neo4j Java驱动实例。
     */
    @Bean
    override fun driver(): Driver {
        val uri = env.getProperty("spring.data.neo4j.uri") ?: "bolt://localhost:7687"
        val username = env.getProperty("spring.data.neo4j.username") ?: "neo4j"
        val password = env.getProperty("spring.data.neo4j.password") ?: "password"
        return GraphDatabase.driver(uri, AuthTokens.basic(username, password))
    }
}

测试代码

@Test
    fun updateAllByName() = parameterized { personRepository ->
        val person = DummyPerson.create()
            .let { personRepository.create(it) }
        val patch = DummyPerson.create()
        
                // 用于验证
        var all = personRepository.findAll(where(Person::name).`is`(person.name)).toList()
        println("all persons ✅: $all")

        val updatedPersons = personRepository.updateAll(
            where(Person::name).`is`(person.name),
            Patch.with {
                it.name = patch.name
                it.age = patch.age
            }
        ).toList()
 
        // 用于验证
        all = personRepository.findAll(where(Person::name).`is`(person.name)).toList()
        println("all persons ❌: $all")

        assertEquals(1, updatedPersons.size)
        val updatedPerson = updatedPersons[0]
        assertEquals(person.id, updatedPerson.id)
        assertEquals(patch.name, updatedPerson.name)
        assertEquals(patch.age, updatedPerson.age)
        assertNotNull(updatedPerson.updatedAt)
    }

测试执行结果

  • 直接调用带Criteria的findAll能获取到节点:all persons ✅: [Person(name=arletha.dietric, age=67)]
  • updateAll内部执行的Cypher语句为:QUERY ➡️ MATCH (n:Person) WHERE n.name = 'arletha.dietric' RETURN n
  • 更新后查询节点未改变:all persons ❌: [Person(name=arletha.dietric, age=67)]
  • 测试因updatedPersons长度为0而失败

内容的提问来源于stack exchange,提问作者Yuniel Acosta Pérez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:55:05