如何在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
相关产品推荐
相关产品推荐

