1. 测试运行时,Mapper 找不到对应的 SQL 映射问题
问题描述:
运行以下测试时,抛出了 NullPointerException
和 Invalid bound statement
错误:
@Test
void testCourseCategory() {
List<CourseCategoryDto> courseCategoryDtos = courseCategoryMapper.selectTreeNodes("1");
System.out.println(courseCategoryDtos);
}
报错信息:
NullPointerException
发生在courseCategoryMapper.selectTreeNodes("1")
处,表示courseCategoryMapper
为null
。org.apache.ibatis.binding.BindingException
提示Invalid bound statement (not found): com.xuecheng.content.mapper.CourseCategoryMapper.selectTreeNodes
,即没有找到对应的 SQL 映射。
分析原因:
- Mapper 映射文件未被正确加载:
- 映射文件放置路径不正确。
mybatis
的配置未正确指向映射文件。
- Mapper 类未被 Spring 管理:
- Mapper 接口未加
@Mapper
注解,或@MapperScan
配置了错误的包路径。
- Mapper 接口未加
解决方案:
-
确保 MyBatis 映射文件放置在
resources
的classpath
下(默认扫描地址),比如: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());
区别分析:
Collectors.toMap()
:- 用途:将
Stream
转换为Map
。 - 实现:通过提供键值对(
keyMapper
和valueMapper
)和冲突解决策略(mergeFunction
)构建Map
。 - 结果:返回一个
Map
。
- 用途:将
map()
:- 用途:用于转换流中的每个元素,返回一个新的
Stream
。 - 实现:对每个元素应用
Function
并生成一个新的流。 - 结果:返回的是流,不能直接得到集合或映射。
- 用途:用于转换流中的每个元素,返回一个新的
总结:
Collectors.toMap()
是一个终端操作,通常用于收集数据。map()
是一个中间操作,通常用于转换流中的元素。
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
中?
分析:
-
categoryTreeDtos
与mapTemp
的对象引用相同:mapTemp
是从courseCategoryTreeDtos
构建的,而categoryTreeDtos
中也包含了courseCategoryTreeDtos
的部分引用。- 修改
mapTemp
中的对象会同步影响到categoryTreeDtos
。
-
categoryTreeDtos
仅保存顶层节点:-
顶层节点是通过以下代码添加的:
if (item.getParentid().equals(id)) { categoryTreeDtos.add(item); }
-
子节点是通过
mapTemp
中的父节点引用来挂载的。
-
验证:引用传递的表现:
修改 mapTemp
中的节点会直接影响到 categoryTreeDtos
中的节点。
4. 引用传递的机制解释
引用传递:
- Java 中对象变量存储的是对象的引用。
- 对引用的修改会直接反映到所有持有该引用的地方。
示例:
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
因为 list1
和 list2
都引用了同一个 node
对象,对其修改在两处都生效。
5. 为什么返回的 categoryTreeDtos
包含子节点?
问题:
如果子节点仅被添加到 mapTemp
中的对象,为什么 categoryTreeDtos
能正确返回包含子节点的结果?
解释:
categoryTreeDtos
中保存的对象本身是从mapTemp
或courseCategoryTreeDtos
中引用的。- 所以当修改
mapTemp
中的对象(如添加子节点)时,categoryTreeDtos
中的相同对象会同步更新。
改进建议:
如果不希望依赖引用传递,可以在构建 categoryTreeDtos
时显式同步子节点:
if (item.getParentid().equals(id)) {
item.setChildrenTreeNodes(mapTemp.get(item.getId()).getChildrenTreeNodes());
categoryTreeDtos.add(item);
}