如何使用MyBatis统计行数并将结果转为Map<String, Long>?
解决MyBatis将GROUP BY查询结果转为Map<String, Long>的问题
先给你拆解下你遇到的两个问题,再给出针对性的解决方案:
问题1:使用resultType="hashmap" + selectOne()抛出TooManyResultsException
这个异常的原因很明确:你的GROUP BY查询返回了3条不同state的统计记录,但selectOne()方法要求MyBatis只能返回0条或1条结果,而你把多条记录丢给它处理,自然就报错了。resultType="hashmap"只是告诉MyBatis把每条记录转成HashMap,但selectOne()无法处理多条结果集合。
问题2:使用@MapKey后遇到JsonGenerationException(Null相关)
这个大概率是因为你的state字段存在NULL值——当你用NULL作为Map的key时,后续的JSON序列化(比如接口返回给前端)会因为JSON规范不允许null作为键而抛出异常。
正确的解决方案(XML映射器场景)
根据你的需求,我推荐两种可行的方式,你可以根据场景选择:
方式1:用DTO配合@MapKey实现优雅转换
这种方式最符合MyBatis的最佳实践,可读性和维护性都很好:
- 定义一个简单的DTO类,用来封装每条GROUP BY的结果:
public class StateOrderCount { private String state; private Long count; // 必须添加getter方法(MyBatis需要通过反射读取字段值) public String getState() { return state; } public void setState(String state) { this.state = state; } public Long getCount() { return count; } public void setCount(Long count) { this.count = count; } }
- 在Mapper接口方法上添加
@MapKey注解,指定用state字段作为Map的key,方法返回Map<String, StateOrderCount>:
import org.apache.ibatis.annotations.MapKey; public interface OrderMapper { @MapKey("state") Map<String, StateOrderCount> getStateOrderCountMap(); }
- XML映射文件中编写查询语句,指定
resultType为你定义的DTO类,同时处理null值:
<select id="getStateOrderCountMap" resultType="com.yourpackage.StateOrderCount"> <!-- 用COALESCE处理state为NULL的情况,避免后续JSON序列化报错 --> SELECT COALESCE(state, 'UNKNOWN') AS state, COUNT(state) AS count FROM orders GROUP BY state; </select>
- 如果需要直接得到
Map<String, Long>,可以在业务层做一次轻量转换:
Map<String, StateOrderCount> tempMap = orderMapper.getStateOrderCountMap(); Map<String, Long> finalMap = new HashMap<>(); tempMap.forEach((key, value) -> finalMap.put(key, value.getCount()));
方式2:直接用List+手动转换(无需DTO)
如果不想定义DTO,也可以直接查询得到List
- Mapper接口定义方法:
public interface OrderMapper { List<Map<String, Object>> getStateOrderCountList(); }
- XML映射文件:
<select id="getStateOrderCountList" resultType="hashmap"> SELECT COALESCE(state, 'UNKNOWN') AS state, COUNT(state) AS count FROM orders GROUP BY state; </select>
- 业务层转换:
List<Map<String, Object>> resultList = orderMapper.getStateOrderCountList(); Map<String, Long> stateCountMap = new HashMap<>(); for (Map<String, Object> item : resultList) { String state = (String) item.get("state"); // 这里因为SQL里用了COALESCE,state不会为null,所以可以直接强转 Long count = ((Number) item.get("count")).longValue(); stateCountMap.put(state, count); }
关键注意点
- 一定要处理
state为NULL的情况:用SQL的COALESCE函数把null替换成类似'UNKNOWN'的默认值,避免Map出现null键导致序列化异常。 - 不要用
selectOne()处理多条结果的查询:如果查询返回多条记录,必须用selectList()或者配合@MapKey让MyBatis自动把List转成Map。
内容的提问来源于stack exchange,提问作者Robotyk
相关产品推荐
相关产品推荐

