MyBatis09-《通用源码指导书:MyBatis源码详解》笔记-cursor包

本系列文章是我从《通用源码指导书:MyBatis源码详解》一书中的笔记和总结
本书是基于MyBatis-3.5.2版本,书作者 易哥 链接里是CSDN中易哥的微博。但是翻看了所有文章里只有一篇简单的介绍这本书。并没有过多的展示该书的魅力。接下来我将自己的学习总结记录下来。如果作者认为我侵权请联系删除,再次感谢易哥提供学习素材。本段说明将伴随整个系列文章,尊重原创,本人已在微信读书购买改书。
版权声明:本文为CSDN博主「架构师易哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/onlinedct/article/details/107306041

1.cursor包

Iterable 接口与 Iterator 接口是大家经常接触的两个接口,它们都代表与迭代操作相关的能力。
Iterator的意思是“迭代器”,Iterable的意思是“可迭代的”。如果一个类是迭代器,则基于它可以实现迭代操作;而如果一个类能够给出一个迭代自身内元素的迭代器,则它就是可迭代的。

因此,Iterable接口非常简单,主要定义了一个 Iterator<T>iterator抽象方法用于返回一个 Iterator对象(在 Jdk 1.8中增加了 forEach方法和 spliterator方法)。

Iterator接口表示一个针对集合的迭代器,Iterator接口定义了迭代器最重要的方法。

  • boolean hasNext:判断当前迭代中是否还有未迭代的元素。
  • E next:返回迭代中的下一个元素。
  • default void remove:从迭代器指向的集合中移除迭代器返回的最后一个元素。默认情况下不支持此操作,因为很容易造成迭代混乱。

在编程开发中,Iterable接口与 Iterator接口经常要用到,我们常用的 for-each就是基于这两个接口实现的。

List<User> userList = new ArrayList<>();
for(User user : userList){
	System.out.println(user);
}

编译后会变成:

List<User> userList = new ArrayList<>();
Iterator var2 = userList.iterator();
while(var2.hasNext()){
	User user = (User)var2.next();
	System.out.println(user);
}

代码能在编译后转化,这是因为 for-each是一个语法糖操作,会由编译器在编译阶段帮我们转化为基本语法。于是,在我们使用 for-each操作对 List中的元素进行遍历时,List作为 Iterable接口的子类先通过 iterator方法给出一个Iterator对象,然后基于 Iterator对象实现 List中所有元素的遍历。

要想查看一段java代码在class文件中的真实形态,最简单的方法是使用集成开发软件找到target目录下对应的class文件后查看,也可以自己使用javac命令编译后再通过相关工具打开对应的class文件查看。

最后我们再总结一下,Iterable 接口表征一个类是可迭代的,Iterator 接口表征一个类是迭代器。

  • 如果一个类能够给出一个迭代器(通过 iterator方法)用来对某个集合中的元素进行迭代,那么这个类可以继承 Iterable接口。
  • 如果一个类本身就是一个迭代器,能够对某个集合展开迭代操作,那么这个类可以继承 Iterator接口。

2.MyBatis中游标的使用

在使用 MyBatis进行数据库查询时,经常会查询到大量的结果。在下面所示的例子中,我们查询到了大量的 User对象,并使用 List接收这些对象。

List<User> userList = userMapper.queryUserBySchoolName(userParam);

但有些时候,我们希望逐一读入和处理查询结果,而不是一次读入整个结果集。因为前者能够减少对内存的占用,这在处理大量的数据时会显得十分必要。游标就能够帮助我们实现这一目的,它支持我们每次从结果集中取出一条结果。

在 MyBatis中使用游标进行查询非常简单,映射文件不需要任何的变动,只需要在映射接口中标明返回值类型是 Cursor即可:

 Cursor<User> queryUserBySchoolName(User user);

然后,便可以用下面代码的方式来接收和处理结果。

 UserMapper userMapper = session.getMapper(UserMapper.class);
 User userParam = new User();
 userParam.setSchoolName("Sunny School");
 Cursor<User> userCursor = userMapper.queryUserBySchoolName(userParam);
 for (User user : userCursor) {
     System.out.println("name : " + user.getName() + " ;  email : " + user.getEmail());
 }

3.游标接口

cursor包中的源码非常简单,只有一个 Cursor接口和默认的实现类 DefaultCursor。
Cursor接口继承了 java.io.Closeable接口和 java.lang.Iterable接口。Closeable接口表征一个类是可以关闭的,调用Closeable 接口中的 close 方法可释放类的对象持有的资源。Iterable接口表征一个类是可以迭代的,这样可以对该类的对象使用 for-each操作。

public interface Cursor<T> extends Closeable, Iterable<T> {
  /**
   * 游标是否开启
   * @return 是否开启
   */
  boolean isOpen();
  /**
   * 是否已经完成了所有遍历
   * @return 是否完成了所有遍历
   */
  boolean isConsumed();
  /**
   * 返回当前元素的索引
   * @return 当前元素的索引
   */
  int getCurrentIndex();
}

4.默认游标

DefaultCursor类是默认的游标。下图是DefaultCursor相关类的类图。通过类图可以看出,DefaultCursor类直接或者间接继承了 Cursor、Closeable、Iterable三个接口,这意味着它必须实现这三个接口定义的所有方法。
在这里插入图片描述

4.1 CursorStatus内部类

CursorStatus 内部类非常简单,是一个表征游标状态的枚举类。

  private enum CursorStatus {
    /**
     * A freshly created cursor, database ResultSet consuming has not started.
     */
    CREATED, // 表征游标新创建,结果集尚未消费
    /**
     * A cursor currently in use, database ResultSet consuming has started.
     */
    OPEN, // 表征游标正在被使用中,结果集正在被消费
    /**
     * A closed cursor, not fully consumed.
     */
    CLOSED, // 表征游标已经被关闭,但其中的结果集未被完全消费
    /**
     * A fully consumed cursor, a consumed cursor is always closed.
     */
    CONSUMED // 表征游标已经被关闭,其中的结果集已经被完全消费
  }

4.2 ObjectWrapperResultHandler内部类

ObjectWrapperResultHandler类继承了ResultHandler接口,是一个简单的结果处理器。
ResultHandler接口在 session包中。ResultHandler接口中定义了一个处理单条结果的 handleResult 方法。该方法的输入参数是一个 ResultContext 对象。ResultContext类是结果上下文,从中可以取出一条结果。

  private static class ObjectWrapperResultHandler<T> implements ResultHandler<T> {
    private T result;
    /**
     * 从结果上下文中取出并处理结果
     * @param context 结果上下文
     */
    @Override
    public void handleResult(ResultContext<? extends T> context) {
      // 取出结果上下文中的一条结果
      this.result = context.getResultObject();
      // 关闭结果上下文
      context.stop();
    }
  }

ObjectWrapperResultHandler内部类只是将结果上下文中的一条结果取出然后放入了自身的 result属性中,并未做进一步的处理。

4.3 CursorIterator内部类

CursorIterator类继承了 Iterator接口,是一个迭代器类。
DefaultCursor类间接继承了 Iterable接口,这意味着它必须通过 iterator方法返回一个Iterator对象。DefaultCursor类返回的Iterator对象就是 CursorIterator对象。
CursorIterator 类作为一个迭代器,实现了判断是否存在下一个元素的 hasNext 方法和返回下一个元素的 next方法。

  private class CursorIterator implements Iterator<T> {
    // 缓存下一个要返回的对象,在next操作中完成写入
    T object;
    // next方法中返回的对象的索引
    int iteratorIndex = -1;
    /**
     * 判断是否还有下一个元素,如果有则顺便写入object中
     * @return 是否还有下一个元素
     */
    @Override
    public boolean hasNext() {
      // 如果object!=null,则显然有下一个对象,就是object本身
      if (object == null) {
        // 判断是否还能获取到新的,顺便放到object中
        object = fetchNextUsingRowBound();
      }
      return object != null;
    }
    /**
     * 返回下一个元素
     * @return 下一个元素
     */
    @Override
    public T next() {
      T next = object;

      if (next == null) { // object中无对象
        // 尝试去获取一个
        next = fetchNextUsingRowBound();
      }
      if (next != null) {
        // 此时,next中是这次要返回的对象。object要么本来为null,要么已经取到next中。故清空
        object = null;
        iteratorIndex++;
        // 返回next中的对象
        return next;
      }
      throw new NoSuchElementException();
    }
    /**
     * 删除当前的元素。不允许该操作,故直接抛出异常
     */
    @Override
    public void remove() {
      throw new UnsupportedOperationException("Cannot remove element from Cursor");
    }
  }

在 CursorIterator类中,无论是判断是否还有下一个元素的hasNext方法还是获取下一个元素的 next 方法,都调用了fetchNextUsingRowBound 方法。该方法是外部类DefaultCursor中的一个非常重要的方法。

4.4 DefaultCursor外部类

  // 结果集处理器
  private final DefaultResultSetHandler resultSetHandler;
  // 该结果集对应的ResultMap信息,来源于Mapper中的<ResultMap>节点
  private final ResultMap resultMap;
  // 返回结果的详细信息
  private final ResultSetWrapper rsw;
  // 结果的起止信息
  private final RowBounds rowBounds;
  // ResultHandler的子类,起到暂存结果的作用
  private final ObjectWrapperResultHandler<T> objectWrapperResultHandler = new ObjectWrapperResultHandler<>();
  // 内部迭代器
  private final CursorIterator cursorIterator = new CursorIterator();
  // 迭代器存在标志位
  private boolean iteratorRetrieved;
  // 游标状态
  private CursorStatus status = CursorStatus.CREATED;
  // 记录已经映射的行
  private int indexWithRowBound = -1;

DefaultCursor 类中大多数方法是用来实现 Cursor、Closeable、Iterable 三个接口的方法。其中 Iterable 接口中定义的 iterator 方法,该方法内使用iteratorRetrieved变量保证了迭代器只能给出一次,防止多次给出造成的访问混乱。

  /**
   * 返回迭代器
   * @return 迭代器
   */
  @Override
  public Iterator<T> iterator() {
    if (iteratorRetrieved) { // 如果迭代器已经给出
      throw new IllegalStateException("Cannot open more than one iterator on a Cursor");
    }
    if (isClosed()) { // 如果游标已经关闭
      throw new IllegalStateException("A Cursor is already closed.");
    }
    // 表明迭代器已经给出
    iteratorRetrieved = true;
    // 返回迭代器
    return cursorIterator;
  }

此外,DefaultCursor 类中重要的方法是fetchNextUsingRowBound 方法和其子方法fetchNextObjectFromDatabase方法。fetchNextObjectFromDatabase方法在每次调用时都会从数据库查询返回的结果集中取出一条结果,而fetchNextUsingRowBound方法则在此基础上考虑了查询时的边界限制条件。于是这两个方法共同完成了在满足边界限制的情况下,每次从结果集中取出一条结果的功能。

  /**
   * 考虑边界限制(翻页限制),从数据库中获取下一个对象
   * @return 下一个对象
   */
  protected T fetchNextUsingRowBound() {
    // 从数据库查询结果中取出下一个对象
    T result = fetchNextObjectFromDatabase();
    while (result != null && indexWithRowBound < rowBounds.getOffset()) { // 如果对象存在但不满足边界限制,则持续读取数据库结果中的下一个,直到边界起始位置
      result = fetchNextObjectFromDatabase();
    }
    return result;
  }

  /**
   * 从数据库获取下一个对象
   * @return 下一个对象
   */
  protected T fetchNextObjectFromDatabase() {
    if (isClosed()) {
      return null;
    }
    try {
      status = CursorStatus.OPEN;
      if (!rsw.getResultSet().isClosed()) { // 结果集尚未关闭
        // 从结果集中取出一条记录,将其转化为对象,并存入到objectWrapperResultHandler中
        resultSetHandler.handleRowValues(rsw, resultMap, objectWrapperResultHandler, RowBounds.DEFAULT, null);
      }
    } catch (SQLException e) {
      throw new RuntimeException(e);
    }

    // 获得存入到objectWrapperResultHandler中的对象
    T next = objectWrapperResultHandler.result;
    if (next != null) { // 读到了新的对象
      // 更改索引,表明记录索引加一
      indexWithRowBound++;
    }

    if (next == null || getReadItemsCount() == rowBounds.getOffset() + rowBounds.getLimit()) { // 没有新对象或者已经到了rowBounds边界
      // 游标内的数据已经消费完毕
      close();
      status = CursorStatus.CONSUMED;
    }
    // 清除objectWrapperResultHandler中的该对象,已准备迎接下一对象
    objectWrapperResultHandler.result = null;
    return next;
  }

fetchNextObjectFromDatabase方法的中文含义为“从数据库获取下一个对象”,从方法名称上看,该方法似乎会从数据库中查询下一条记录。但实际上并非如此,该方法并不会引发数据库查询操作。因为,在该方法被调用之前,数据库查询的结果集已经完整地保存在了 rsw变量中。fetchNextObjectFromDatabase方法只是从结果集中取出下一条记录,而非真正地去数据库查询下一条记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值