如何使用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+版本支持。
额外注意事项
- POJO构造函数:确保你的
Loan和Person类有无参构造函数,jOOQ的into(Class)方法依赖它来实例化对象。 - 自定义类型映射:如果你的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
相关产品推荐
相关产品推荐

