多表关联大平层转JSON树形结构

比如把这种平层数据转化为下面这种树形结构树

json 复制代码
[
  {
    "id": 2,
    "parentId": null,
    "name": "有声书",
    "type": "category",
    "children": [
      {
        "id": 1,
        "parentId": 2,
        "name": "有声书分类",
        "type": "attribute",
        "children": [
          {"id": 1, "name": "男频小说", "type": "value"},
          {"id": 2, "name": "女频小说", "type": "value"}
        ]
      }
    ]
  }
]

方法1:非递归建树

首先创建一个实体类,作为返回给前端的json结构

java 复制代码
@Data
class TreeNode {
    private Long id;
    private Long parentId;
    private String name;
    private List<TreeNode> children;
}

核心方法

java 复制代码
/**
 * 核心:三层平层数据 转 树形结构
 * @param flatList 平层数据(分类+属性+属性值)
 * @return 树形数据
 */
public List<TreeNode> buildTree(List<FlatData> flatList) {
    // 1. 核心容器:key=节点ID,value=节点(自动去重)
    Map<Long, TreeNode> nodeMap = new HashMap<>();

    // 2. 遍历平层数据,封装三层节点
    for (FlatData data : flatList) {
        // 分类(第一层)
        buildNode(nodeMap, data.getCategoryId(), null, data.getCategoryName());
        // 属性(第二层,父=分类ID)
        buildNode(nodeMap, data.getAttributeId(), data.getCategoryId(), data.getAttributeName());
        // 属性值(第三层,父=属性ID)
        buildNode(nodeMap, data.getValueId(), data.getAttributeId(), data.getValueName());
    }

    // 3. 核心挂载:子节点 → 父节点 children
    for (TreeNode node : nodeMap.values()) {
        Long parentId = node.getParentId();
        if (parentId != null && nodeMap.containsKey(parentId)) {
            nodeMap.get(parentId).getChildren().add(node);
        }
    }

    // 4. 返回根节点(parentId=null)
    return nodeMap.values().stream()
            .filter(node -> node.getParentId() == null)
            .toList();
}

// 通用节点构建(去重)
private void buildNode(Map<Long, TreeNode> map, Long id, Long parentId, String name) {
    if (id == null || map.containsKey(id)) return;
    TreeNode node = new TreeNode();
    node.setId(id);
    node.setParentId(parentId);
    node.setName(name);
    node.setChildren(new ArrayList<>());
    map.put(id, node);
}

2.分组嵌套

很简单的一种写法,就是层级需要固定

java 复制代码
// 1. 查询平层宽表数据
List<FlatAttributeDTO> flatList = attributeMapper.selectFlatData(category1Id);
List<JSONObject> result = new ArrayList<>();

// 第一层:按 一级分类ID 分组
Map<Long, List<FlatAttributeDTO>> groupByCategory = flatList.stream()
    .collect(Collectors.groupingBy(FlatAttributeDTO::getCategory1Id));

// 组装一级分类节点
groupByCategory.forEach((categoryId, categoryList) -> {
    JSONObject categoryNode = new JSONObject();
    categoryNode.put("id", categoryId);
    categoryNode.put("name", categoryList.get(0).getCategory1Name());
    List<JSONObject> attributeList = new ArrayList<>();

    // 第二层:组内按 属性ID 分组
    Map<Long, List<FlatAttributeDTO>> groupByAttribute = categoryList.stream()
        .collect(Collectors.groupingBy(FlatAttributeDTO::getAttributeId));

    // 组装属性节点
    groupByAttribute.forEach((attributeId, attributeListData) -> {
        JSONObject attributeNode = new JSONObject();
        attributeNode.put("id", attributeId);
        attributeNode.put("name", attributeListData.get(0).getAttributeName());
        List<JSONObject> valueList = new ArrayList<>();

        // 第三层:遍历属性值(叶子节点)
        for (FlatAttributeDTO dto : attributeListData) {
            JSONObject valueNode = new JSONObject();
            valueNode.put("id", dto.getValueId());
            valueNode.put("name", dto.getValueName());
            valueList.add(valueNode);
        }

        attributeNode.put("children", valueList);
        attributeList.add(attributeNode);
    });

    categoryNode.put("children", attributeList);
    result.add(categoryNode);
});

3.使用MybatisPlus的ResultMap映射

实体类

这里的实体类异地昂要是这种,里面有list这种

java 复制代码
// 一级分类(根节点)
public class CategoryTree {
    private Long id;            // category1_id
    private String name;        // category1_name
    private List<CategoryLevel2> children;
    // getter/setter 略
}

// 二级分类
public class CategoryLevel2 {
    private Long id;            // category2_id
    private String name;        // category2_name
    private List<CategoryLevel3> children;
    // getter/setter 略
}

// 三级分类(叶子节点)
public class CategoryLevel3 {
    private Long id;            // category3_id
    private String name;        // category3_name
    // getter/setter 略
}

mapper文件

mapper中最外层就是我们的最外的是体力,里面的collection可以嵌套对应实体类里面的list说白了还是一一对应的关系

xml 复制代码
<resultMap id="categoryTreeMap" type="com.xxx.dto.CategoryTree" autoMapping="false">
    <!-- 一级分类 -->
    <id column="c1_id" property="id"/>
    <result column="c1_name" property="name"/>
    
    <!-- 二级分类集合 -->
    <collection property="children" ofType="com.xxx.dto.CategoryLevel2">
        <id column="c2_id" property="id"/>
        <result column="c2_name" property="name"/>
        
        <!-- 三级分类集合 -->
        <collection property="children" ofType="com.xxx.dto.CategoryLevel3">
            <id column="c3_id" property="id"/>
            <result column="c3_name" property="name"/>
        </collection>
    </collection>
</resultMap>

<select id="selectAttributeTree" resultMap="attributeTreeMap">
    SELECT 
        bc.category1_id AS c1_id,
        bc.category1_name AS c1_name,
        ba.attribute_id AS a_id,
        ba.attribute_name AS a_name,
        bav.value_id AS v_id,
        bav.value_name AS v_name
    FROM base_category bc
    INNER JOIN base_attribute ba 
        ON bc.category1_id = ba.category1_id
    INNER JOIN base_attribute_value bav 
        ON ba.attribute_id = bav.attribute_id
    WHERE bc.category1_id = #{category1Id}
</select>
相关推荐
ja哇2 小时前
大厂面试高频八股
java·面试·职场和发展
yoyo_zzm2 小时前
Laravel6.x新特性全解析
java·spring boot·后端
Nick_zcy2 小时前
小说在线阅读网站和小说管理系统 · 功能全解析
java·后端·python·springboot·ruoyi
源码宝3 小时前
基于 SpringBoot + Vue 的医院随访系统:技术架构与功能实现
java·vue.js·spring boot·架构·源码·随访系统·随访管理
qinqinzhang3 小时前
Java 中的 IoC、AOP、MVC
java
禾叙_3 小时前
【langchain4j】结构化输出(六)
java·开发语言
饭小猿人3 小时前
Android 腾讯X5WebView如何禁止系统自带剪切板和自定义剪切板视图
android·java
Advancer-4 小时前
第二次蓝桥杯总结(上)
java·算法·职场和发展·蓝桥杯
\xin4 小时前
pikachu自编SQL(POST)
java·数据库·sql