MyBatis查询两个字段,返回Map,一个字段作为key,一个字段作为value的实现

项目场景:

  在使用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=王五)

总结 

  1. Map<String, Object>只能接收单条数据。
  2. @MapKey指定key,可以接受多条数据,会把查询得到的每一行数据封装成一个Map(key=id,value=Map),然后把所有的Map又以字段名为key,都封装进一个Map或者实体类中。
  3. ResultHandle可以实现一个字段作为key,一个字段作为value的效果,但是比较麻烦些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涛哥是个大帅比

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值