项目场景:
在使用MyBatis,我们经常会遇到这种情况:SELECT两个字段,需要返回一个Map,其中第一个字段作为key,第二个字段作为value。MyBatis的MapKey虽然很实用,但并不能解决这种场景。
问题描述
第一种情况
我们查询出id和name两个字段,想用Map来接收它,然后想着直接MyBatis直接返回Map对象就好了
Mapper:
这种情况结果只能一条记录,多条记录会报错:nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
/**
* 结果只能一条记录,多条记录会报错
*/
@Select("select id, name from t_test where id = 1")
Map<String, Object> selectOne();
Service测试:
@Service
public class TestService{
@Autowired
private TestMapper testMapper;
public void selectOne() {
Map<String, Object> map = testMapper.selectOne();
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
}
}
打印的结果:
key:name,value:张三
key:id,value:1
发现打印的结果跟想的不一样 ,想的结果是key:1,value:张三,因为MyBatis是把结果以("id":1)、("name":"张三")的形式保存在Map中的。
第二种情况
然后使用@MapKey改进
Mapper:
这样就可以接收多条记录了
/**
* Map接收多条记录
*/
@Select("select id, name from t_test")
@MapKey("id") //指定key
Map<Integer, Map<String, Object>> tooMany();
Service测试:
@Service
public class TestService{
@Autowired
private TestMapper testMapper;
public void tooMany() {
Map<Integer, Map<String, Object>> map = testMapper.tooMany();
for (Map.Entry<Integer, Map<String, Object>> entry : map.entrySet()) {
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
}
}
打印的结果:
key:1,value:{name=张三, id=1}
key:2,value:{name=李四, id=2}
key:3,value:{name=王五, id=3}
数据是都能正常获取了,可还不是想要的格式,想的结果是key:1,value:张三。
解决方案:
如果要实现id字段作为key,name字段作为value,可以使用mybatis提供的ResultHandler的结果处理器,他能感知到你的每条数据(可以当做limit 1来理解),拿到该条数据你加工下,继续拿下一条数据,有点类似于for循环,每次拿到数据放到map中,最后拿到这个map。
1.xml
首先xml写好sql,@select注解也可以,但是没有xml方便,因为需要返回resultMap
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.dao.TestMapper">
<!--这里key-value报红不影响-->
<resultMap id="mapResult" type="map">
<result property="key" column="id"/>
<result property="value" column="name"/>
</resultMap>
<select id="resultHandler" resultMap="mapResult">
select id, name from t_test
</select>
</mapper>
这里大家会发现key和value会报红,其实不影响,如果实在觉得不舒服,那么可以把result这两行注释掉变成:
<resultMap id="mapResult" type="map">
<!--<result property="key" column="id"/>-->
<!--<result property="value" column="name"/>-->
</resultMap>
但是这样的话,后面map获取值就要改成:map.get("id"), map.get("name"),就不能兼容其它的表使用。
2.Mapper
返回值必须为void,有其它参数可以加在mapResultHandler参数之前比如:
resultHandler(Integer id, MapResultHandler<Integer, String> mapResultHandler)
/**
* 使用ResultHandler实现key-value接收多条记录
* 返回值必须为void,有其它参数可以加在mapResultHandler参数之前比如:
* resultHandler(Integer id, MapResultHandler<Integer, String> mapResultHandler)
*/
void resultHandler(MapResultHandler<Integer, String> mapResultHandler);
3.ResultHandle
对应xml配置的key-value
public class MapResultHandler<K,V> implements ResultHandler<Map<K,V>> {
private final Map<K,V> mappedResults = new HashMap<>();
@Override
public void handleResult(ResultContext context) {
Map map = (Map) context.getResultObject();
mappedResults.put((K)map.get("key"), (V)map.get("value"));
}
public Map<K,V> getMappedResults() {
return mappedResults;
}
}
4.Service测试:
@Service
public class TestService{
@Autowired
private TestMapper testMapper;
public void resultHandler() {
MapResultHandler<Integer, String> resultHandler = new MapResultHandler<>();
testMapper.resultHandler(resultHandler);
Map<Integer, String> map = resultHandler.getMappedResults();
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
}
}
5.打印结果:
key:1,value:张三
key:2,value:李四
key:3,value:王五
达到了想要的效果,但是代码量也增加了,为了实现key-value牺牲太大。
备用方案:
使用ResultHandler的解决方案比较麻烦,还是使用@MapKey比较方便,就是获取value的时候再多一步,除了使用Map接收,还可以使用实体类接收。
1.创建实体类
// User实体类
public class User {
private Integer id;
private String name;
// 省略getter和setter方法
}
2.Mapper
/**
* 实体类接收多条记录
*/
@Select("select id, name from t_test")
@MapKey("id") //指定key
Map<Integer, User> getUser();
3. Service测试:
@Service
public class TestService{
@Autowired
private TestMapper testMapper;
public void getUser() {
Map<Integer, User> map = testMapper.getUser();
for (Map.Entry<Integer, User> entry : map.entrySet()) {
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
}
}
4.打印结果:
key:1,value:User(id=1, name=张三)
key:2,value:User(id=2, name=李四)
key:3,value:User(id=3, name=王五)
总结
- Map<String, Object>只能接收单条数据。
- @MapKey指定key,可以接受多条数据,会把查询得到的每一行数据封装成一个Map(key=id,value=Map),然后把所有的Map又以字段名为key,都封装进一个Map或者实体类中。
- ResultHandle可以实现一个字段作为key,一个字段作为value的效果,但是比较麻烦些。