mybatis之ResultHandler如何使用

ResultHandler字面上意思就是对查询结果处理。

-->mybatis官网

想要理解ResultHandler需要知道一下源码,查询过程是怎么跑的

ResultHandler: 参数允许你按你喜欢的方式处理每一行。你可以将它添加到 List 中、创建 Map 和 Set,或者丢弃每个返回值都可以,它取代了仅保留执行语句过后的总结果列表的死板结果。你可以使用 ResultHandler 做很多事,并且这是 MyBatis 自身内部会使用的方法,以创建结果集列表。

由于网上对ResultHandler使用的文章介绍过少,于是我就自己去摸索了。

1.探索之前准备好一个小例子

我准备了一张user表


user表
2.自己搭建好一个简单的mybatis框架
mybatis
3.先看看官方文档
image.png
官方告诉了我们SqlSession这个类里面应该只有select方法才有ResultHandler参数
然后我们再去看myabtis里面的SqlSession这个类
SqlSession

果然只有select方法才会对ResultHandler进行处理
对比之下selectList是没有的

接下来需要了解mybatis查询在源码当中是经历了什么过程才能知道我们查询的时候什么时候才会去走select方法.

接下来我大致讲下源码怎么走,关键是走到哪里才会运用到ResultHandler
        String resource = "mybatis-cfg.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取mapper,返回的mapper是经过jdk动态代理的
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        Optional<User> user = userMapper.selectOneById(1);
        System.out.println(user.get().getName());
        sqlSession.close();

先从sqlsession.getMapper(UserMapper.class)代码说起,它是调用了DefaultSqlSession里面的getMapper方法,我们继续追踪,追踪到MapperRegistry这个类里面的getMapper方法。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

我们再继续点进去mapperProxyFactory.newInstance(sqlSession);这个方法
接下来我们进入到MapperProxyFactory 这个类

MapperProxyFactory

最终返回的UserMapper是经过MapperProxy这个代理类的类

接下来我们得到UserMapper就要跑Optional<User> user = userMapper.selectOneById(1);
这个代码。运行这行代码前,因为userMapper是被代理过的类。那么selectOneById之前会先去调用MapperProxy这个类的invoke方法.
我们去到MapperProxy会看到下面的invoke方法

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

最后实际调用查询方法是mapperMethod.execute(sqlSession, args);
我们点execute追踪下去来到MapperMethod这个类的execute方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
这部分代码意思是你进行增删改查,它会根据条件去匹配方法。

重点来了

我们着重去看SELECT部分,根据最上面文档我们知道调用SqlSession的select方法才会去用到ResultHandler,我们继续SELECT里面一个一个方法去看,最终发现executeWithResultHandler(sqlSession, args);这个方法里面才调用select方法。

当然我是怎么知道的呢,因为我按照文档写了一个方法试着传了ResultHandler进去发现ResultHandler并没有用。
public interface UserMapper {
    User selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
}

UserMapper.xml

<?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.xiaowan.UserMapper">
    <sql id="Base_Column_List" >
      id, name, age
    </sql>

    <select id="selectByPrimaryKey" resultType="com.xiaowan.User">
        select
        <include refid="Base_Column_List" />
        from user
        where id = #{id}
    </select>
</mapper>

简单的查询方法,对了我们需要自己新建一个自己的ResultHandler类去实现ResultHandler

/**
 * @Description 为了测试,结果处理类的作用只是简单的将名字+上hello
 * @Author wbm
 * @Date 2019/10/11 0011 下午 04:48
 **/
public class MyResultHandler implements ResultHandler<User> {

    private User result = null;

    @Override
    public void handleResult(ResultContext<? extends User> resultContext) {

        User user = resultContext.getResultObject();
        user.setName(user.getName() + "hello");
        result = user;
    }

    public User getReuslt(){
        return result;
    }
}

MyResultHandler 我主要只是简单将返回的结果的名字加上hello,实际根据需求去变换

为什么我传入的ResultHandler并没有产生作用呢,debug追踪,发现

image.png

我的代码并有去走executeWithResultHandler(sqlSession, args);这个方法,我们目的是要让它走executeWithResultHandler(sqlSession, args);这个方法,它才会去调用我们的ResultHandler类。为什么一定要走这个方法,这个需要自己去深入看看这个executeWithResultHandler(sqlSession, args);方法了。

没走进去,就说明判断条件不成立,我们看看判断条件

method.returnsVoid():返回类型是void
method.hasResultHandler():是否拥有ResultHandler这个参数
根据判断条件,我们需要修改一下我们的UserMapper方法
将原来的User返回类型改为void

public interface UserMapper {
    void selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
}

接下来修改下原来的查询方法,见下图


image.png

这样就成功了。

注意点

有时候简单的语句我们会直接在方法上面注解@Select然后写sql

public interface UserMapper {
    @Select("select * from user where id=#{id}")
    void selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
}
error

会出现以上错误,是我们使用注解sql的时候,还必须得使用@ResultType声明返回类型,将上面的UserMapper修改为如下

public interface UserMapper {
    @ResultType(User.class)
    @Select("select * from user where id=#{id}")
    void selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);
}

细节点:当结果是返回多条的时候

我们在UserMapper定义个按年龄(qryUserList)查询,这样会返回多条数据

public interface UserMapper {
    @ResultType(User.class)
    @Select("select * from user where id=#{id}")
    void selectByPrimaryKey(Integer id, ResultHandler<User> resultHandler);

    @ResultType(User.class)
    @Select("select * from user where age = #{age} order by id asc")
    void qryUserList(int age, ResultHandler<User> handler);
}

MyResultHandler :resultContext.getResultObject()这个方法里面只是会储存一条数据,意思假设你搜索出来的结果有3条分别是A,B,C,那么它就会跑3次MyResultHandler

public class MyResultHandler implements ResultHandler<User> {

    private List<User> result = new ArrayList<>(10);

    @Override
    public void handleResult(ResultContext<? extends User> resultContext) {
        User user = resultContext.getResultObject();
        user.setName(user.getName() + "hello");
        result.add(user);
    }

    public List<User> getReuslt(){
        return result;
    }
}
resultContext.stop();  这个方法作用,当你找出3条数据ABC,
但是你根据条件匹配到只要A的数据,那么在第一次对比A的时候你发现正确,不想继续匹配BC数据,就可以用这个方法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容