本系列文章是我从《通用源码指导书:MyBatis源码详解》一书中的笔记和总结
本书是基于MyBatis-3.5.2版本,书作者 易哥 链接里是优快云中易哥的微博。但是翻看了所有文章里只有一篇简单的介绍这本书。并没有过多的展示该书的魅力。接下来我将自己的学习总结记录下来。如果作者认为我侵权请联系删除,再次感谢易哥提供学习素材。本段说明将伴随整个系列文章,尊重原创,本人已在微信读书购买改书。
版权声明:本文为优快云博主「架构师易哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/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方法只是从结果集中取出下一条记录,而非真正地去数据库查询下一条记录。