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

如何在Java中将一对多查询结果集映射为对象

嗨,这个问题我太熟了——用JOIN查出来的嵌套关联结果集,手动转对象很容易写出一堆重复代码,确实得找个优雅的路子。我给你分享几个常用的方案,看你场景选:

方案1:手动映射+缓存(轻量无依赖)

如果不想引入额外框架,这是最直接的优化方式。核心思路是用Map缓存已经创建好的父/子对象,避免重复实例化——毕竟JOIN结果里会有大量重复的父记录行。

举个例子,假设你的对象模型是:

class Parent {
    private Long id;
    private String name;
    private List<Child> children = new ArrayList<>();
    // getter/setter
}

class Child {
    private Long id;
    private String name;
    private List<Grandchild> grandchildren = new ArrayList<>();
    // getter/setter
}

class Grandchild {
    private Long id;
    private String name;
    // getter/setter
}

然后遍历ResultSet的代码可以这么写:

Map<Long, Parent> parentMap = new HashMap<>();
Map<Long, Child> childMap = new HashMap<>();

while (rs.next()) {
    // 处理父对象
    Long parentId = rs.getLong("parent_id");
    Parent parent = parentMap.get(parentId);
    if (parent == null) {
        parent = new Parent();
        parent.setId(parentId);
        parent.setName(rs.getString("parent_name"));
        parentMap.put(parentId, parent);
    }

    // 处理子对象
    Long childId = rs.getLong("child_id");
    if (childId != null && !rs.wasNull()) { // 避免子记录为空的情况
        Child child = childMap.get(childId);
        if (child == null) {
            child = new Child();
            child.setId(childId);
            child.setName(rs.getString("child_name"));
            parent.getChildren().add(child);
            childMap.put(childId, child);
        }

        // 处理孙对象
        Long grandchildId = rs.getLong("grandchild_id");
        if (grandchildId != null && !rs.wasNull()) {
            Grandchild grandchild = new Grandchild();
            grandchild.setId(grandchildId);
            grandchild.setName(rs.getString("grandchild_name"));
            child.getGrandchildren().add(grandchild);
        }
    }
}

// 最终得到去重后的父对象列表
List<Parent> result = new ArrayList<>(parentMap.values());

这个方法的关键是用Map缓存已经创建的对象,跳过重复的父/子记录,只处理新的行,避免对象重复创建和重复添加。

方案2:用MyBatis的ResultMap自动映射(优雅省心)

如果你的项目已经在用MyBatis,那这绝对是最优雅的方案。只需要在Mapper里定义好嵌套的ResultMap,写原生SQL就能自动帮你把ResultSet转成嵌套对象。

比如定义ResultMap:

<resultMap id="grandchildMap" type="Grandchild">
    <id column="grandchild_id" property="id"/>
    <result column="grandchild_name" property="name"/>
</resultMap>

<resultMap id="childMap" type="Child">
    <id column="child_id" property="id"/>
    <result column="child_name" property="name"/>
    <collection property="grandchildren" resultMap="grandchildMap"/>
</resultMap>

<resultMap id="parentMap" type="Parent">
    <id column="parent_id" property="id"/>
    <result column="parent_name" property="name"/>
    <collection property="children" resultMap="childMap"/>
</resultMap>

然后Mapper接口里写原生SQL的方法:

@Select("SELECT p.id parent_id, p.name parent_name, " +
        "c.id child_id, c.name child_name, " +
        "gc.id grandchild_id, gc.name grandchild_name " +
        "FROM parent p " +
        "LEFT JOIN child c ON p.id = c.parent_id " +
        "LEFT JOIN grandchild gc ON c.id = gc.child_id " +
        "WHERE p.id = #{parentId}")
@ResultMap("parentMap")
Parent getParentWithNestedChildren(Long parentId);

MyBatis会自动处理JOIN结果里的重复行,帮你把嵌套的子对象、孙对象都组装好,完全不用手动处理缓存和遍历逻辑,代码非常干净。

方案3:Spring JDBC的自定义RowMapper

如果用的是Spring JDBC,也可以自定义RowMapper来处理嵌套映射,思路和手动映射类似,但可以整合Spring的BeanPropertyRowMapper来简化属性赋值。

比如:

public class ParentRowMapper implements RowMapper<Parent> {
    private Map<Long, Parent> parentCache = new HashMap<>();
    private Map<Long, Child> childCache = new HashMap<>();

    @Override
    public Parent mapRow(ResultSet rs, int rowNum) throws SQLException {
        Long parentId = rs.getLong("parent_id");
        Parent parent = parentCache.get(parentId);
        if (parent == null) {
            parent = new Parent();
            parent.setId(parentId);
            parent.setName(rs.getString("parent_name"));
            parentCache.put(parentId, parent);
        }

        Long childId = rs.getLong("child_id");
        if (!rs.wasNull()) {
            Child child = childCache.get(childId);
            if (child == null) {
                child = new Child();
                child.setId(childId);
                child.setName(rs.getString("child_name"));
                parent.getChildren().add(child);
                childCache.put(childId, child);
            }

            Long grandchildId = rs.getLong("grandchild_id");
            if (!rs.wasNull()) {
                Grandchild grandchild = new Grandchild();
                grandchild.setId(grandchildId);
                grandchild.setName(rs.getString("grandchild_name"));
                child.getGrandchildren().add(grandchild);
            }
        }
        return parent;
    }

    // 最后要去重,因为返回的列表里会有重复的父对象
    public List<Parent> getUniqueParents(List<Parent> mappedRows) {
        return new ArrayList<>(new LinkedHashSet<>(mappedRows));
    }
}

使用的时候:

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
ParentRowMapper rowMapper = new ParentRowMapper();
List<Parent> rawResult = jdbcTemplate.query(sql, rowMapper);
List<Parent> finalResult = rowMapper.getUniqueParents(rawResult);
总结
  • 如果项目无ORM依赖,选手动映射+缓存,轻量可控;
  • 如果已经用MyBatis,直接用嵌套ResultMap,最优雅省心;
  • 如果用Spring JDBC,自定义RowMapper整合缓存逻辑即可。

内容的提问来源于stack exchange,提问作者johnny-b-goode

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:23:56