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

如何使用Reactor与R2dbc实现祖孙层级关联数据的完整查询填充

解决方案:嵌套关联的响应式查询填充

首先先修正实体类里的两处语法错误(少逗号),避免编译失败:

@Entity
@Table(name = "child", schema = "public", catalog = "Test")
data class MyChildObject(
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    @org.springframework.data.annotation.Id
    @Column(name = "id")
    var id: Int = 0,
    @Column(name = "parent_id")
    var parentId: Int? = null, // 补全缺失逗号
    @Transient
    var grandchildData: List<MyGrandchildObject>? = null
)

@Entity
@Table(name = "grandchild", schema = "public", catalog = "Test")
data class MyGrandchildObject(
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    @org.springframework.data.annotation.Id
    @Column(name = "id")
    var id: Int = 0, // 补全缺失逗号
    @Column(name = "child_id")
    var childId: Int? = null
)

接下来你只需要调整关联数据填充的逻辑,把Child和Grandchild的填充流程嵌套起来就行——完全可以复用你已经写好的addGrandchildData方法,不需要修改实体类属性类型。修改后的代码如下:

fun viewAllParents(): Mono<MutableList<MyParentObject>> = parentRepository.findAll()
    .flatMap { parent ->
        // 为当前Parent填充带Grandchild数据的Child列表
        Mono.just(parent).addChildWithGrandchildData(parent.id)
    }
    .collectList()

// 新增方法:为Parent填充已关联Grandchild的Child数据
fun Mono<MyParentObject>.addChildWithGrandchildData(parentId: Int): Mono<MyParentObject> = 
    this.zipWith(
        // 查询当前Parent下的所有Child,同时为每个Child填充Grandchild
        childRepository.getAllByParentIdEquals(parentId)
            .flatMap { child ->
                Mono.just(child).addGrandchildData(child.id)
            }
            .collectList()
    )
    .map { tuple ->
        tuple.t1.childData = tuple.t2
        tuple.t1
    }

// 复用你已实现的方法,注意修正变量名一致性
fun Mono<MyChildObject>.addGrandchildData(id: Int): Mono<MyChildObject> = 
    this.zipWith(childOfChildRepository.getAllByChildIdEquals(id).collectList())
        .map {
            it.t1.grandchildData = it.t2 // 这里要和实体类属性名保持一致
            it.t1
        }

关键思路说明:

  1. 响应式嵌套串联:R2DBC是异步非阻塞模型,我们用flatMap来层级处理查询——先查Parent,再查该Parent下的所有Child,接着对每个Child触发Grandchild的查询,最后把所有结果组装到实体的临时属性中。
  2. 复用已有逻辑:你已经实现了addGrandchildData,直接在Child的查询流中调用即可,不需要重复造轮子。
  3. 无需修改属性类型:完全不需要把grandchildData改成Flux,我们通过collectList()把Flux<MyGrandchildObject>转换成Mono<List<MyGrandchildObject>>,正好可以赋值给实体类里的List<MyGrandchildObject>?属性。

额外优化建议:

  • 如果数据量较大,可以考虑在Repository层编写JOIN语句的关联查询,减少数据库交互次数,提升性能;如果数据量不大,当前写法的可读性和维护性已经足够好。
  • 注意变量名一致性:你之前的代码里写了childOfChildData,但实体类属性是grandchildData,这个小细节要修正,避免赋值失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 09:27:37