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

如何将JSON数组列映射为非主键关联的Child实体List或Map?

实现JSON数组字段到Child实体集合/映射的两种方案

一、映射为List<Child>

方案1:SQL关联查询+结果映射(推荐,性能更优)

利用数据库JSON函数拆分child_keys数组,关联child表获取完整实体,再通过ORM框架聚合映射到Parent对象中。

以MySQL为例,查询SQL如下:

SELECT 
    p.id AS parent_id,
    p.child_keys,
    c.id AS child_id,
    c.`key` AS child_key
FROM parent p
LEFT JOIN child c ON JSON_CONTAINS(p.child_keys, CONCAT('"', c.`key`, '"'))
WHERE p.id = ?

MyBatis结果映射配置

<resultMap id="ParentWithChildrenMap" type="Parent">
    <id column="parent_id" property="id"/>
    <result column="child_keys" property="childKeys" jdbcType="JSON"/>
    <collection property="children" ofType="Child">
        <id column="child_id" property="id"/>
        <result column="child_key" property="key"/>
    </collection>
</resultMap>

Spring Data JDBC写法

@Query("SELECT p.id AS parent_id, p.child_keys, c.id AS child_id, c.`key` AS child_key FROM parent p LEFT JOIN child c ON JSON_CONTAINS(p.child_keys, CONCAT('"', c.`key`, '"')) WHERE p.id = :id")
Parent findByIdWithChildren(@Param("id") Long id);

方案2:自定义TypeHandler

通过自定义类型处理器,读取JSON数组后查询对应Child实体并组装为List:

public class ChildListTypeHandler extends BaseTypeHandler<List<Child>> {

    private final JdbcTemplate jdbcTemplate;

    public ChildListTypeHandler(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<Child> parameter, JdbcType jdbcType) throws SQLException {
        List<String> keys = parameter.stream().map(Child::getKey).collect(Collectors.toList());
        ps.setString(i, new ObjectMapper().writeValueAsString(keys));
    }

    @Override
    public List<Child> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String json = rs.getString(columnName);
        if (json == null) return Collections.emptyList();
        List<String> keys = new ObjectMapper().readValue(json, new TypeReference<List<String>>() {});
        return jdbcTemplate.query("SELECT * FROM child WHERE `key` IN (:keys)", 
            new MapSqlParameterSource("keys", keys), 
            (rs2, rowNum) -> new Child(rs2.getLong("id"), rs2.getString("key")));
    }

    @Override
    public List<Child> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String json = rs.getString(columnIndex);
        // 同上面的解析逻辑
        return Collections.emptyList();
    }

    @Override
    public List<Child> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String json = cs.getString(columnIndex);
        // 同上面的解析逻辑
        return Collections.emptyList();
    }
}

实体类配置:

class Parent {
    @JdbcTypeCode(SqlTypes.JSON)
    @Column(name = "child_keys")
    List<String> childKeys;

    @Column(name = "child_keys")
    @TypeHandler(ChildListTypeHandler.class)
    List<Child> children;
}

二、映射为Map<String, Child>

方案1:SQL关联查询+结果映射

在MyBatis中通过结果映射指定Map的键为Child的key字段:

<resultMap id="ParentWithChildMap" type="Parent">
    <id column="parent_id" property="id"/>
    <result column="child_keys" property="childKeys" jdbcType="JSON"/>
    <collection property="children" ofType="Child" javaType="java.util.Map" keyColumn="child_key" valueColumn="child_id">
        <id column="child_id" property="id"/>
        <result column="child_key" property="key"/>
    </collection>
</resultMap>

方案2:自定义TypeHandler

修改TypeHandler逻辑,将查询结果组装为Map:

public class ChildMapTypeHandler extends BaseTypeHandler<Map<String, Child>> {

    private final JdbcTemplate jdbcTemplate;

    public ChildMapTypeHandler(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Map<String, Child> parameter, JdbcType jdbcType) throws SQLException {
        List<String> keys = new ArrayList<>(parameter.keySet());
        ps.setString(i, new ObjectMapper().writeValueAsString(keys));
    }

    @Override
    public Map<String, Child> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String json = rs.getString(columnName);
        if (json == null) return Collections.emptyMap();
        List<String> keys = new ObjectMapper().readValue(json, new TypeReference<List<String>>() {});
        List<Child> children = jdbcTemplate.query("SELECT * FROM child WHERE `key` IN (:keys)", 
            new MapSqlParameterSource("keys", keys), 
            (rs2, rowNum) -> new Child(rs2.getLong("id"), rs2.getString("key")));
        return children.stream().collect(Collectors.toMap(Child::getKey, Function.identity()));
    }

    @Override
    public Map<String, Child> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String json = rs.getString(columnIndex);
        // 同上面的解析逻辑
        return Collections.emptyMap();
    }

    @Override
    public Map<String, Child> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String json = cs.getString(columnIndex);
        // 同上面的解析逻辑
        return Collections.emptyMap();
    }
}

实体类配置:

class Parent {
    @JdbcTypeCode(SqlTypes.JSON)
    @Column(name = "child_keys")
    List<String> childKeys;

    @Column(name = "child_keys")
    @TypeHandler(ChildMapTypeHandler.class)
    Map<String, Child> children;
}

注意事项

  • SQL关联方案性能更优,仅需一次查询;TypeHandler方案会触发额外查询,可能出现N+1问题。
  • 不同数据库的JSON函数语法不同,需根据实际数据库调整SQL(比如PostgreSQL用jsonb_array_elements_text)。
  • MyBatis需确保TypeHandler注册到配置中;Spring Data JDBC会自动识别自定义TypeHandler。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 04:13:14