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

Spring R2DBC如何结合Query by Example实现多表关联查询返回自定义DTO

解决方案:Spring R2DBC QBE实现多表关联查询返回自定义DTO

ReactiveQueryByExampleExecutor是为对应数据库表的实体类设计的,你的CustomQueryResultDTO没有映射表,所以无法直接为它创建Repository接口。下面是三种可行的实现方案:

方案一:基于主实体QBE查询 + 手动关联映射DTO

选择一个核心实体(比如Customer),用QBE查询该实体的结果,再通过实体间的关联关系(或数据库外键)关联Account和Address,最后映射到DTO。

步骤1:定义实体类及关联关系

假设实体间的关联是:Customer ↔ Account(一对一,外键customer_id),Account ↔ Address(一对一,外键account_id):

@Table("customer")
public class Customer {
    @Id
    private Long id;
    private String name;
    @Column("account_id")
    private Long accountId;
    // getter/setter
}

@Table("account")
public class Account {
    @Id
    private Long id;
    private String accountNumber;
    @Column("address_id")
    private Long addressId;
    // getter/setter
}

@Table("address")
public class Address {
    @Id
    private Long id;
    private String addressCode;
    // getter/setter
}

步骤2:创建主实体的Repository

public interface CustomerRepository extends ReactiveCrudRepository<Customer, Long>, ReactiveQueryByExampleExecutor<Customer> {
}

步骤3:Service层实现动态查询与DTO映射

@Service
public class QueryService {
    private final CustomerRepository customerRepo;
    private final ReactiveCrudRepository<Account, Long> accountRepo;
    private final ReactiveCrudRepository<Address, Long> addressRepo;

    public QueryService(CustomerRepository customerRepo, 
                        ReactiveCrudRepository<Account, Long> accountRepo, 
                        ReactiveCrudRepository<Address, Long> addressRepo) {
        this.customerRepo = customerRepo;
        this.accountRepo = accountRepo;
        this.addressRepo = addressRepo;
    }

    public Flux<CustomQueryResultDTO> queryWithExample(Customer example) {
        Example<Customer> customerExample = Example.of(example);
        
        return customerRepo.findAll(customerExample)
                .flatMap(customer -> accountRepo.findById(customer.getAccountId())
                        .map(account -> new Tuple2<>(customer, account)))
                .flatMap(tuple -> addressRepo.findById(tuple.getT2().getAddressId())
                        .map(address -> {
                            CustomQueryResultDTO dto = new CustomQueryResultDTO();
                            dto.setAddressCode(address.getAddressCode());
                            dto.setAccountNumber(tuple.getT2().getAccountNumber());
                            dto.setName(tuple.getT1().getName());
                            return dto;
                        }));
    }
}

方案二:QBE生成条件 + 原生SQL关联查询

如果关联逻辑复杂,可利用QBE生成查询条件,拼接成原生关联SQL,再通过R2dbcEntityTemplate执行并映射到DTO。

步骤1:利用Example生成查询条件

手动解析Example生成SQL where子句(可参考Spring Data R2DBC内部的QueryCreator逻辑扩展):

private String buildWhereClause(Example<Customer> example) {
    StringBuilder where = new StringBuilder();
    Customer probe = example.getProbe();
    
    if (probe.getName() != null) {
        where.append("customer.name = :name");
    }
    // 按需添加其他字段的条件拼接
    return where.length() > 0 ? " WHERE " + where : "";
}

步骤2:执行原生关联查询并映射DTO

@Service
public class QueryService {
    private final R2dbcEntityTemplate template;

    public QueryService(R2dbcEntityTemplate template) {
        this.template = template;
    }

    public Flux<CustomQueryResultDTO> queryWithExample(Customer example) {
        Example<Customer> customerExample = Example.of(example);
        String whereClause = buildWhereClause(customerExample);
        
        String sql = """
            SELECT a.address_code, acc.account_number, c.name
            FROM customer c
            JOIN account acc ON c.account_id = acc.id
            JOIN address a ON acc.address_id = a.id
            """ + whereClause;

        Bindings bindings = Bindings.empty();
        if (example.getName() != null) {
            bindings = bindings.bind("name", example.getName());
        }

        return template.getDatabaseClient()
                .sql(sql)
                .bindings(bindings)
                .map(row -> {
                    CustomQueryResultDTO dto = new CustomQueryResultDTO();
                    dto.setAddressCode(row.get("address_code", String.class));
                    dto.setAccountNumber(row.get("account_number", String.class));
                    dto.setName(row.get("name", String.class));
                    return dto;
                })
                .all();
    }
}

方案三:实体关联 + DTO投影(适合固定关联逻辑)

如果查询的关联关系固定,仅需动态过滤主实体条件,可在实体间建立关联,用DTO作为投影类结合QBE查询。

步骤1:修改实体添加关联

@Table("customer")
public class Customer {
    @Id
    private Long id;
    private String name;
    @OneToOne
    @JoinColumn(name = "account_id")
    private Account account;
    // getter/setter
}

@Table("account")
public class Account {
    @Id
    private Long id;
    private String accountNumber;
    @OneToOne
    @JoinColumn(name = "address_id")
    private Address address;
    // getter/setter
}

步骤2:QBE查询并投影到DTO

public Flux<CustomQueryResultDTO> queryWithExample(Customer example) {
    Example<Customer> customerExample = Example.of(example);
    return customerRepo.findAll(customerExample)
            .map(customer -> {
                CustomQueryResultDTO dto = new CustomQueryResultDTO();
                dto.setName(customer.getName());
                dto.setAccountNumber(customer.getAccount().getAccountNumber());
                dto.setAddressCode(customer.getAccount().getAddress().getAddressCode());
                return dto;
            });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 17:31:05