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

如何在R2DBC与Spring WebFlux中实现多表关联查询

R2DBC+Spring WebFlux 多表关联查询实现方案

Spring Data R2DBC 本身不支持传统ORM框架的自动关联映射注解(如@OneToOne、@ManyToOne),需要手动完成关联查询和结果组装,以下是3种常用实现方案:


前置准备:调整实体适配R2DBC映射规则

原实体中Person直接嵌套Address对象不符合R2DBC的基础映射规则,数据库层person表仅存储address表的外键add_id,调整后实体如下:

// Person 实体
@Table("person")
class Person {
    @Id
    private Integer id;
    private String name;
    private Integer addId; // 存储address表外键,而非直接嵌套Address对象
    // 省略getter、setter、全参构造方法
}

// Address 实体
@Table("address")
class Address {
    @Id
    private Integer addId;
    private String city;
    private String state;
    private String country;
    private Integer zip;
    // 省略getter、setter、全参构造方法
}

自定义返回DTO用于封装关联查询结果:

class PersonWithAddressVO {
    private Integer id;
    private String name;
    private Address address;
    // 省略getter、setter、全参构造方法
}

方案1:DatabaseClient 手写关联SQL(推荐,性能最优)

直接通过DatabaseClient执行JOIN语句,手动映射结果集,适合复杂关联、大查询量场景:

@Service
public class PersonService {
    @Autowired
    private DatabaseClient databaseClient;

    // 查询所有人员关联地址信息
    public Flux<PersonWithAddressVO> listAllWithAddress() {
        String joinSql = """
                SELECT p.id AS person_id, p.name, a.add_id, a.city, a.state, a.country, a.zip
                FROM person p LEFT JOIN address a ON p.add_id = a.add_id
                """;
        return databaseClient.sql(joinSql)
                .map((row, metadata) -> {
                    // 组装Address对象
                    Address address = new Address(
                            row.get("add_id", Integer.class),
                            row.get("city", String.class),
                            row.get("state", String.class),
                            row.get("country", String.class),
                            row.get("zip", Integer.class)
                    );
                    // 组装返回DTO
                    return new PersonWithAddressVO(
                            row.get("person_id", Integer.class),
                            row.get("name", String.class),
                            address
                    );
                })
                .all();
    }
}

如果需要查询单条数据,将末尾的.all()替换为.one()即可,返回类型改为Mono<PersonWithAddressVO>。


方案2:Repository 分查询组装(适合小数据量简单场景)

通过Spring Data R2DBC的响应式Repository分别查询两张表的数据,再通过响应式操作符组装结果,代码更简洁但存在N+1查询问题:

// 定义Repository
public interface PersonRepository extends ReactiveCrudRepository<Person, Integer> {}
public interface AddressRepository extends ReactiveCrudRepository<Address, Integer> {}

// 业务层组装逻辑
@Service
public class PersonService {
    @Autowired
    private PersonRepository personRepository;
    @Autowired
    private AddressRepository addressRepository;

    public Flux<PersonWithAddressVO> listAllWithAddress() {
        return personRepository.findAll()
                // 对每个Person查询对应的Address
                .flatMap(person -> addressRepository.findById(person.getAddId())
                        .map(address -> new PersonWithAddressVO(person.getId(), person.getName(), address))
                );
    }
}

方案3:Repository + @Query 注解实现

直接在Repository接口中通过@Query写关联SQL,框架会自动映射结果到自定义DTO,要求DTO字段名与SQL返回的列名完全匹配:

public interface PersonRepository extends ReactiveCrudRepository<Person, Integer> {
    @Query("""
            SELECT p.id, p.name, a.add_id, a.city, a.state, a.country, a.zip
            FROM person p LEFT JOIN address a ON p.add_id = a.add_id
            """)
    Flux<PersonWithAddressVO> findAllWithAddress();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.16 21:15:01