MyBatis返回Optional<List<VO>>遇TooManyResultsException问题咨询
Optional<List<VO>>时的TooManyResultsException问题 首先得搞明白为啥会报错:MyBatis是根据方法返回类型来决定执行selectList还是selectOne的。当你写Optional<List<VO>>时,MyBatis会把它当成单个对象的Optional包装,所以会调用selectOne执行查询。但你的SQL返回的是多条记录,这就触发了TooManyResultsException——毕竟selectOne只期望拿到0或1条结果。
还有个关键细节:MyBatis查询集合类型(比如List<VO>)时,永远不会返回null,只会返回空的List。所以大多数情况下,根本没必要用Optional包裹集合,直接返回List<VO>就足够安全了。
不过如果你确实需要返回Optional<List<VO>>,这里有两种靠谱的实现方式:
方式一:用默认方法手动包装(推荐)
保持MyBatis的Mapper方法返回标准的List<VO>,然后通过接口的默认方法把结果转换成Optional:
public interface TestMapper { @Transactional(readOnly = true) List<VO> selectData(); // 默认方法处理Optional包装 default Optional<List<VO>> selectDataOpt() { List<VO> dataList = selectData(); // 集合为空返回Optional.empty(),否则返回包装后的Optional return dataList.isEmpty() ? Optional.empty() : Optional.of(dataList); } }
对应的XML不用修改,还是原来的配置:
<select id="selectData" resultType="VO"> SELECT ID, NAME, CALL_NUM FROM USER ORDER BY INSERTDATE ASC LIMIT 100 </select>
这种方式既符合MyBatis的设计规范,又能满足你返回Optional的需求,而且完全不会触发异常。
方式二:强制让MyBatis识别集合类型(不推荐,可读性差)
如果你不想加额外的方法,可以通过@ResultType注解明确告诉MyBatis集合的元素类型,让它知道应该用selectList执行查询:
public interface TestMapper { @Transactional(readOnly = true) @ResultType(VO.class) // 指定集合的元素类型,让MyBatis识别这是集合查询 Optional<List<VO>> selectDataOpt(); }
XML配置保持不变。不过这种方式可读性稍差,其他维护代码的人可能会困惑注解的作用,所以更推荐第一种方式。
最后再提一句:除非有特别的业务需求必须用Optional包裹集合,否则直接返回List<VO>是最简洁的做法——MyBatis已经帮你把null集合处理成空List了,完全不用额外操心空指针问题。
内容的提问来源于stack exchange,提问作者hojunchoi

