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

Spring Boot MongoDB获取三级子菜单遇空指针异常求解决

问题描述

在Spring Boot集成MongoDB环境中,需根据ID查询三级嵌套的子菜单数据,但多次尝试均失败。

数据结构示例

{
   "title": "box", 
   "active": false,
   "level": "you",
   "children": [
        {
            "id": 1,
            "title": "hood",
            "type": "uod",
            "active": false,
            "level": "9",
            "children": [
             { 
                 "id":1,
                "title": "New",
                "type": "hog",
                "active": false,
                "level": "3"
                 }
             ]
        }
   ]
}

编写的API代码

@GetMapping("/menus/{id}/submenu/{subId}/children/{childId}")
public List<?> findChildrenById(@PathVariable("id") long id, 
    @PathVariable("subId") long subId, @PathVariable("childId") long childId) { 
        Query query = new Query(); 
        query.addCriteria(Criteria.where("id").is(id).and("children.id").is(subId).and("children.children.id").is(childId)); 
        return operations.findOne(query, SubMenu.class).getChildren();
}

错误信息

java.lang.NullPointerException: Cannot invoke "com.menu.entity.SubMenu.getChildren()" because the return value of "org.springframework.data.mongodb.core.MongoOperations.findOne(org.springframework.data.mongodb.core.query.Query, java.lang.Class)" is null
    at com..menu.controller.MenuItemsGetControlller.findChildrenById(MenuItemsGetControlller.java:84)
解决方案

1. 先处理空指针异常

findOne返回null说明无匹配文档,直接调用getChildren()必然触发空指针,必须先做非空判断:

@GetMapping("/menus/{id}/submenu/{subId}/children/{childId}")
public List<?> findChildrenById(@PathVariable("id") long id, 
    @PathVariable("subId") long subId, @PathVariable("childId") long childId) { 
        Query query = new Query(); 
        query.addCriteria(Criteria.where("id").is(id)
                .and("children.id").is(subId)
                .and("children.children.id").is(childId)); 
        
        SubMenu menu = operations.findOne(query, SubMenu.class);
        if (menu == null) {
            // 返回空列表或自定义未找到响应
            return Collections.emptyList();
            // 也可抛出异常:throw new ResponseStatusException(HttpStatus.NOT_FOUND, "目标菜单不存在");
        }
        // 后续处理三级子菜单逻辑
}

2. 修正查询逻辑,精准定位三级子菜单

原查询仅匹配存在对应ID的文档,但不会过滤嵌套数组元素,且返回的是二级菜单而非目标三级菜单。需通过投影或聚合查询来精准获取目标数据:

方式一:使用投影过滤数组

@GetMapping("/menus/{id}/submenu/{subId}/children/{childId}")
public List<?> findChildrenById(@PathVariable("id") long id, 
    @PathVariable("subId") long subId, @PathVariable("childId") long childId) { 
        Query query = new Query(); 
        query.addCriteria(Criteria.where("id").is(id)
                .and("children.id").is(subId)
                .and("children.children.id").is(childId));
        
        // 投影只返回匹配的三级子菜单
        query.fields()
                .include("children.$.children.$")
                .exclude("_id");
        
        Map<String, Object> result = operations.findOne(query, Map.class);
        if (result == null) {
            return Collections.emptyList();
        }
        
        // 提取三级子菜单
        List<Map<String, Object>> secondLevel = (List<Map<String, Object>>) result.get("children");
        if (secondLevel == null || secondLevel.isEmpty()) {
            return Collections.emptyList();
        }
        List<Map<String, Object>> thirdLevel = (List<Map<String, Object>>) secondLevel.get(0).get("children");
        return thirdLevel != null ? thirdLevel : Collections.emptyList();
}

方式二:使用Aggregation聚合查询(更灵活)

适合复杂嵌套结构的查询:

@GetMapping("/menus/{id}/submenu/{subId}/children/{childId}")
public List<?> findChildrenById(@PathVariable("id") long id, 
    @PathVariable("subId") long subId, @PathVariable("childId") long childId) { 
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("id").is(id)),
                Aggregation.unwind("children"),
                Aggregation.match(Criteria.where("children.id").is(subId)),
                Aggregation.unwind("children.children"),
                Aggregation.match(Criteria.where("children.children.id").is(childId)),
                Aggregation.project("children.children").andExclude("_id")
        );
        
        AggregationResults<Map> results = operations.aggregate(aggregation, "your_collection_name", Map.class);
        List<Map> mappedResults = results.getMappedResults();
        
        List<Object> thirdLevelMenus = new ArrayList<>();
        for (Map map : mappedResults) {
            thirdLevelMenus.add(map.get("children"));
        }
        
        return thirdLevelMenus.isEmpty() ? Collections.emptyList() : thirdLevelMenus;
}

3. 检查实体类匹配

确保SubMenu实体类与MongoDB文档结构完全匹配:

  • 根菜单id字段类型需与数据库一致(若数据库是int或字符串,需同步修改实体类类型)
  • children字段需用List<SubMenu>映射嵌套结构,示例如下:
public class SubMenu {
    private Long id;
    private String title;
    private boolean active;
    private String level;
    private String type;
    private List<SubMenu> children;
    
    // Getters & Setters
}

内容的提问来源于stack exchange,提问作者tauheed Alzohar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 11:35:24