1. 测试运行时,Mapper 找不到对应的 SQL 映射问题

问题描述:

运行以下测试时,抛出了 NullPointerExceptionInvalid bound statement 错误:

@Test
void testCourseCategory() {
    List<CourseCategoryDto> courseCategoryDtos = courseCategoryMapper.selectTreeNodes("1");
    System.out.println(courseCategoryDtos);
}

报错信息:

  1. NullPointerException
    发生在 courseCategoryMapper.selectTreeNodes("1") 处,表示 courseCategoryMappernull
  2. org.apache.ibatis.binding.BindingException
    提示 Invalid bound statement (not found): com.xuecheng.content.mapper.CourseCategoryMapper.selectTreeNodes,即没有找到对应的 SQL 映射。
分析原因:
  1. Mapper 映射文件未被正确加载:
    • 映射文件放置路径不正确。
    • mybatis 的配置未正确指向映射文件。
  2. Mapper 类未被 Spring 管理:
    • Mapper 接口未加 @Mapper 注解,或 @MapperScan 配置了错误的包路径。
解决方案:
  1. 确保 MyBatis 映射文件放置在 resourcesclasspath 下(默认扫描地址),比如:

    src/main/resources/mapper/CourseCategoryMapper.xml

2. 关于 Collectors.toMap()map() 的区别

问题描述:

对以下两种操作的区别感到困惑:

stream.collect(Collectors.toMap(key -> key.getId(), value -> value, (key1, key2) -> key2));

stream.map(x -> x.someOperation());
区别分析:
  1. Collectors.toMap()
    • 用途:将 Stream 转换为 Map
    • 实现:通过提供键值对(keyMappervalueMapper)和冲突解决策略(mergeFunction)构建 Map
    • 结果:返回一个 Map
  2. map()
    • 用途:用于转换流中的每个元素,返回一个新的 Stream
    • 实现:对每个元素应用 Function 并生成一个新的流。
    • 结果:返回的是流,不能直接得到集合或映射。
总结:

3. 关于 queryTreeNodes 方法中 mapTemp 的引用机制

方法代码:
public List<CourseCategoryDto> queryTreeNodes(String id) {
    List<CourseCategoryDto> courseCategoryTreeDtos = courseCategoryMapper.selectTreeNodes(id);

    Map<String, CourseCategoryDto> mapTemp = courseCategoryTreeDtos.stream()
        .filter(item -> !id.equals(item.getId()))
        .collect(Collectors.toMap(key -> key.getId(), value -> value, (key1, key2) -> key2));

    List<CourseCategoryDto> categoryTreeDtos = new ArrayList<>();

    courseCategoryTreeDtos.stream()
        .filter(item -> !id.equals(item.getId()))
        .forEach(item -> {
            if (item.getParentid().equals(id)) {
                categoryTreeDtos.add(item);
            }
            CourseCategoryDto courseCategoryTreeDto = mapTemp.get(item.getParentid());
            if (courseCategoryTreeDto != null) {
                if (courseCategoryTreeDto.getChildrenTreeNodes() == null) {
                    courseCategoryTreeDto.setChildrenTreeNodes(new ArrayList<>());
                }
                courseCategoryTreeDto.getChildrenTreeNodes().add(item);
            }
        });

    return categoryTreeDtos;
}
问题描述:

为什么子节点直接放在从 mapTemp 查出来的对象中,而不是放在返回的 categoryTreeDtos 中?

分析:
  1. categoryTreeDtosmapTemp 的对象引用相同:

    • mapTemp 是从 courseCategoryTreeDtos 构建的,而 categoryTreeDtos 中也包含了 courseCategoryTreeDtos 的部分引用。
    • 修改 mapTemp 中的对象会同步影响到 categoryTreeDtos
  2. categoryTreeDtos 仅保存顶层节点:

    • 顶层节点是通过以下代码添加的:

      if (item.getParentid().equals(id)) {
          categoryTreeDtos.add(item);
      }
    • 子节点是通过 mapTemp 中的父节点引用来挂载的。

验证:引用传递的表现:

修改 mapTemp 中的节点会直接影响到 categoryTreeDtos 中的节点。


4. 引用传递的机制解释

引用传递:
示例:
CourseCategoryDto node = new CourseCategoryDto();
List<CourseCategoryDto> list1 = new ArrayList<>();
List<CourseCategoryDto> list2 = new ArrayList<>();

list1.add(node);
list2.add(node);

node.setName("Updated Name");

System.out.println(list1.get(0).getName()); // 输出: Updated Name
System.out.println(list2.get(0).getName()); // 输出: Updated Name

因为 list1list2 都引用了同一个 node 对象,对其修改在两处都生效。


5. 为什么返回的 categoryTreeDtos 包含子节点?

问题:

如果子节点仅被添加到 mapTemp 中的对象,为什么 categoryTreeDtos 能正确返回包含子节点的结果?

解释:
改进建议:

如果不希望依赖引用传递,可以在构建 categoryTreeDtos 时显式同步子节点:

if (item.getParentid().equals(id)) {
    item.setChildrenTreeNodes(mapTemp.get(item.getId()).getChildrenTreeNodes());
    categoryTreeDtos.add(item);
}