Jooq含1:N关联的1:1关系映射:能否无需显式RecordMapper?
可行方案:实现隐式映射减少样板代码
方案一:利用jOOQ嵌套POJO自动映射
通过调整查询的投影结构,使其与DTO的嵌套层级完全匹配,jOOQ可自动完成映射,无需手动编写RecordMapper。
步骤与代码示例:
- 确保DTO字段与数据库字段对应(可通过
@Column注解指定映射关系,或保持字段名一致) - 重构查询投影,将User及其Roles封装为嵌套结构:
dsl() .select( // 选择Product的所有字段 Product.PRODUCT.fields(), // 将User字段和roles封装为嵌套row,对应ProductDTO的creator字段 row( User.USER.fields(), multiset( select(UserRole.USER_ROLE.fields()) .from(UserRole.USER_ROLE) .where(UserRole.USER_ROLE.USERID.eq(Product.PRODUCT.CREATORID)) ).as("roles") ).as("creator") ) .from(Product.PRODUCT) .leftJoin(User.USER) .on(User.USER.USERID.eq(Product.PRODUCT.CREATORID)) .where(...) .groupBy(Product.PRODUCT.PRODUCTID, User.USER.USERID) // 需包含User主键避免分组问题 .orderBy(...) .offset(...) .limit(...) // 直接自动映射到ProductDTO .fetchInto(ProductDTO.class);
核心要求:
- ProductDTO需包含
UserDTO creator字段 - UserDTO需包含
List<UserRoleDTO> roles字段 - 若字段名不匹配,可在DTO字段上添加
@Column(name = "数据库字段名")注解做映射
方案二:全局配置RecordMapperProvider
通过自定义RecordMapperProvider,将映射逻辑全局注册到jOOQ配置中,所有查询操作(fetch、fetchStream等)都会自动复用该逻辑。
步骤与代码示例:
- 实现自定义RecordMapperProvider:
public class CustomRecordMapperProvider implements RecordMapperProvider { @Override public <R extends Record, E> RecordMapper<R, E> provide(RecordType<R> recordType, Class<E> type) { if (type == ProductDTO.class) { return record -> { ProductDTO product = record.into(ProductDTO.class); UserDTO creator = record.into(UserDTO.class); List<UserRoleDTO> creatorRoles = record.get("roles", Result.class).into(UserRoleDTO.class); creator.setRoles(creatorRoles); product.setCreator(creator); return product; }; } // 其他类型使用默认映射器 return new DefaultRecordMapper<>(recordType, type); } }
- 将Provider配置到jOOQ的Configuration中(以Spring Boot为例):
@Bean public DefaultConfiguration jooqConfiguration(DataSource dataSource) { DefaultConfiguration configuration = new DefaultConfiguration(); configuration.set(dataSource); configuration.set(SQLDialect.MYSQL); // 替换为你的数据库方言 configuration.set(new CustomRecordMapperProvider()); return configuration; }
- 查询时直接调用
fetchInto即可:
dsl() .select( asterisk(), multiset( selectDistinct(UserRole.USER_ROLE.fields()) .from(UserRole.USER_ROLE) .where(UserRole.USER_ROLE.USERID.eq(Product.PRODUCT.CREATORID)) ).as("roles") ) .from(Product.PRODUCT) .leftJoin(User.USER) .on(User.USER.USERID.eq(Product.PRODUCT.CREATORID)) .where(...) .groupBy(Product.PRODUCT.PRODUCTID) .orderBy(...) .offset(...) .limit(...) .fetchInto(ProductDTO.class);
方案三:结合Converter处理Multiset到List的转换
针对multiset返回结果,自定义Converter将其直接转换为List<UserRoleDTO>,配合jOOQ自动映射简化逻辑。
步骤与代码示例:
- 实现Converter:
public class MultisetToUserRolesConverter implements Converter<Object, List<UserRoleDTO>> { @Override public List<UserRoleDTO> from(Object object) { if (object == null) return Collections.emptyList(); Result<?> result = (Result<?>) object; return result.into(UserRoleDTO.class); } @Override public Object to(List<UserRoleDTO> userRoleDTOS) { // 写入场景一般无需实现,返回null即可 return null; } @Override public Class<Object> fromType() { return Object.class; } @Override public Class<List<UserRoleDTO>> toType() { return (Class<List<UserRoleDTO>>) (Class<?>) List.class; } }
- 在UserDTO的roles字段上添加注解:
public class UserDTO { // 其他字段... @Converter(converter = MultisetToUserRolesConverter.class) private List<UserRoleDTO> roles; // getter/setter }
- 查询时确保投影的
roles别名与UserDTO字段名匹配,直接用fetchInto(ProductDTO.class)完成映射即可。
内容的提问来源于stack exchange,提问作者funkrusher
相关产品推荐
相关产品推荐

