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

如何使用JOOQ将数据映射至含列表字段的POJO

嘿,这个问题我太熟了!用jOOQ处理带关联集合的POJO映射,其实有几种实用的方案,我给你拆解下最常用的几个,你可以根据自己的场景选:

方法一:用fetchGroups()手动分组映射

这是最直接的方式,适合快速实现,核心思路是先关联查询所有数据,再按主表(loan)分组,最后把关联的person列表设置到对应的Loan对象里。

假设你的jOOQ生成了对应表的对象(LOAN、PERSON、LOAN_PERSON_REL),代码示例如下:

import java.util.*;
import java.util.stream.Collectors;
import org.jooq.DSLContext;
import static com.yourpackage.generated.Tables.*;

public List<Loan> getLoansWithPersons(DSLContext dsl) {
    // 1. 关联三张表,查询所有需要的字段
    var result = dsl.select(LOAN.fields())
                    .select(PERSON.fields())
                    .from(LOAN)
                    .leftJoin(LOAN_PERSON_REL).on(LOAN.LINE.eq(LOAN_PERSON_REL.LOAN_LINE))
                    .leftJoin(PERSON).on(LOAN_PERSON_REL.PERSON_ID.eq(PERSON.ID))
                    .fetch();

    // 2. 按Loan对象分组,得到每个Loan对应的Person列表
    Map<Loan, List<Person>> loanPersonMap = result.fetchGroups(
        // 把记录映射为Loan对象
        record -> record.into(LOAN).into(Loan.class),
        // 把记录映射为Person对象,注意处理left join带来的null值
        record -> record.into(PERSON) != null ? record.into(PERSON).into(Person.class) : null
    );

    // 3. 清理空值并将Person列表绑定到Loan
    loanPersonMap.forEach((loan, persons) -> {
        List<Person> validPersons = persons.stream()
                                           .filter(Objects::nonNull)
                                           .collect(Collectors.toList());
        loan.setPersons(validPersons);
    });

    return new ArrayList<>(loanPersonMap.keySet());
}

优点:代码直观,不需要额外配置;缺点:如果POJO字段很多,或者有多层嵌套结构,手动处理会稍显繁琐。

方法二:自定义RecordMapper处理关联

如果你的映射逻辑更复杂(比如需要处理重复数据、自定义字段转换),可以写一个自定义的RecordMapper,利用缓存避免重复创建Loan对象:

import org.jooq.Record;
import org.jooq.RecordMapper;
import java.util.HashMap;
import java.util.Map;

public class LoanWithPersonsMapper implements RecordMapper<Record, Loan> {
    private final Map<Integer, Loan> loanCache = new HashMap<>();

    @Override
    public Loan map(Record record) {
        // 从缓存获取或创建Loan对象
        Integer loanLine = record.getValue(LOAN.LINE);
        Loan loan = loanCache.get(loanLine);
        
        if (loan == null) {
            loan = record.into(LOAN).into(Loan.class);
            loan.setPersons(new ArrayList<>());
            loanCache.put(loanLine, loan);
        }

        // 映射Person并添加到Loan的列表中(避免重复添加)
        Person person = record.into(PERSON).into(Person.class);
        if (person != null && !loan.getPersons().contains(person)) {
            loan.getPersons().add(person);
        }

        return loan;
    }
}

然后在查询中使用这个映射器:

public List<Loan> getLoansWithPersons(DSLContext dsl) {
    var loans = dsl.select(LOAN.fields())
                   .select(PERSON.fields())
                   .from(LOAN)
                   .leftJoin(LOAN_PERSON_REL).on(LOAN.LINE.eq(LOAN_PERSON_REL.LOAN_LINE))
                   .leftJoin(PERSON).on(LOAN_PERSON_REL.PERSON_ID.eq(PERSON.ID))
                   .fetchInto(new LoanWithPersonsMapper());

    // 去重,因为映射器会返回重复的Loan对象
    return loans.stream().distinct().collect(Collectors.toList());
}

优点:适合复杂映射场景,缓存机制提升性能;缺点:需要额外编写映射器类,代码量稍大。

进阶方案:用jOOQ 3.14+的MULTISET特性(推荐)

如果你用的是jOOQ 3.14及以上版本,强烈推荐用MULTISET,它支持直接在SQL层面定义嵌套集合,映射到POJO的列表字段,代码更简洁优雅:

import org.jooq.impl.DSL;
import static org.jooq.impl.DSL.*;

public List<Loan> getLoansWithPersons(DSLContext dsl) {
    return dsl.select(
                LOAN.LINE,
                LOAN.BALANCE,
                LOAN.EXPIRED,
                LOAN.STATE,
                LOAN.DEFAULT_DAYS,
                LOAN.BRANCH,
                LOAN.BALANCE_HISTORY,
                LOAN.EXPIRED_HISTORY,
                LOAN.LOAN_CODE,
                LOAN.EXTRA_INFO,
                LOAN.LOAN_TYPE,
                LOAN.NOM_PRO,
                // 定义嵌套的Person集合
                MULTISET(
                    select(PERSON.fields())
                    .from(PERSON)
                    .join(LOAN_PERSON_REL).on(PERSON.ID.eq(LOAN_PERSON_REL.PERSON_ID))
                    .where(LOAN_PERSON_REL.LOAN_LINE.eq(LOAN.LINE))
                ).as("persons").convertFrom(r -> r.into(Person.class))
            )
            .from(LOAN)
            .fetchInto(Loan.class);
}

优点:查询和映射一体化,无需手动分组或处理重复数据,jOOQ自动完成嵌套集合的映射;缺点:需要jOOQ 3.14+版本支持。


额外注意事项

  1. POJO构造函数:确保你的Loan和Person类有无参构造函数,jOOQ的into(Class)方法依赖它来实例化对象。
  2. 自定义类型映射:如果你的POJO包含枚举(比如LoanTypes)或自定义对象(比如Branch),需要为这些类型配置jOOQ的Converter或Binding,确保数据库类型和Java类型能正确转换。比如枚举的转换器示例:
import org.jooq.Converter;

public class LoanTypesConverter implements Converter<String, LoanTypes> {
    @Override
    public LoanTypes from(String dbValue) {
        return dbValue != null ? LoanTypes.valueOf(dbValue) : null;
    }

    @Override
    public String to(LoanTypes javaValue) {
        return javaValue != null ? javaValue.name() : null;
    }

    @Override
    public Class<String> fromType() {
        return String.class;
    }

    @Override
    public Class<LoanTypes> toType() {
        return LoanTypes.class;
    }
}

你可以在jOOQ代码生成配置中指定这个转换器,或者在查询时手动调用convert()方法。
3. 关联类型选择:如果只需要查询有关联Person的Loan,可以用inner join替代left join,这样就不需要处理null的Person对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:14:25