源码分析-Mybatis源码阅读-游标

源码解析-Mybatis
01 源码分析-Mybatis源码阅读准备
02 源码分析-Mybatis源码阅读-会话层
03 源码分析-Mybatis源码阅读-执行器
04 源码分析-Mybatis源码阅读-Statement语句处理器
05 源码分析-Mybatis源码阅读-参数处理器
06 源码分析-Mybatis源码阅读-结果集处理器
07 源码分析-Mybatis源码阅读-主键生成器
08 源码分析-Mybatis源码阅读-懒加载机制
09 源码分析-Mybatis源码阅读-游标
10 源码分析-Mybatis源码阅读-类型处理器
11 源码分析-Mybatis源码阅读-缓存
12 源码分析-Mybatis源码阅读-Mapper代理

一 游标说明

游标的代码主要在org.apache.ibatis.cursor包下,主要包含一个 org.apache.ibatis.cursor.Cursor接口类和一个org.apache.ibatis.cursor.defaults.DefaultCursor实现类。

游标作用:

  • 游标协定以使用Iterator延迟处理获取行数据。
  • 标非常适合处理通常不适合内存的数百万个项目查询。
  • 如果在resultMaps中使用集合,则必须使用resultMap的id列对游标SQL查询进行排序(resultOrdered =“ true”)。

二 游标源码解读

Cursor游标接口类源码解读如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 游标协定以使用Iterator延迟处理获取行数据。
* 游标非常适合处理通常不适合内存的数百万个项目查询。
* 如果在resultMaps中使用集合,则必须使用resultMap的id列对游标SQL查询进行排序(resultOrdered =“ true”)。
* Cursor contract to handle fetching items lazily using an Iterator.
* Cursors are a perfect fit to handle millions of items queries that would not normally fits in memory.
* If you use collections in resultMaps then cursor SQL queries must be ordered (resultOrdered="true")
* using the id columns of the resultMap.
*
* @author Guillaume Darmont / guillaume@dropinocean.com
*/
public interface Cursor<T> extends Closeable, Iterable<T> {

/**
* 如果游标已开始从数据库中获取项目,则返回true
* @return true if the cursor has started to fetch items from database.
*/
boolean isOpen();

/**
* 如果游标已被完全消耗,并且已返回所有与查询匹配的元素,则返回true。
* @return true if the cursor is fully consumed and has returned all elements matching the query.
*/
boolean isConsumed();

/**
* 获取当前项目索引。第一项的索引为0。
* Get the current item index. The first item has the index 0.
*
* @return -1 if the first cursor item has not been retrieved. The index of the current item retrieved.
*/
int getCurrentIndex();
}

DefaultCursor默认游标实现源码解读如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/**
* This is the default implementation of a MyBatis Cursor.
* This implementation is not thread safe.
*
* @author Guillaume Darmont / guillaume@dropinocean.com
*/
public class DefaultCursor<T> implements Cursor<T> {

/** 默认结果集处理类 */
// ResultSetHandler stuff
private final DefaultResultSetHandler resultSetHandler;
/** 结果map映射关系 */
private final ResultMap resultMap;
/** 结果集包装类 */
private final ResultSetWrapper rsw;
/** 翻页限制 物理分页 */
private final RowBounds rowBounds;
/** 对象包装结果处理类 */
protected final ObjectWrapperResultHandler<T> objectWrapperResultHandler = new ObjectWrapperResultHandler<>();

/** 游标迭代器 */
private final CursorIterator cursorIterator = new CursorIterator();
/** 确保不能多次打开游标,只能打开一次 */
private boolean iteratorRetrieved;

/** 游标的默认状态是创建 */
private CursorStatus status = CursorStatus.CREATED;
/** 分页对象的索引位置为-1 */
private int indexWithRowBound = -1;

/**
* 游标的状态
*/
private enum CursorStatus {

/**
* 新创建的游标,数据库ResultSet还未被消费
* A freshly created cursor, database ResultSet consuming has not started.
*/
CREATED,
/**
* 当前正在使用的游标已开始消费数据库ResultSet
* 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
}

/**
* 构造器 创建一个默认的游标
* @param resultSetHandler 默认结果集处理类
* @param resultMap 结果映射map
* @param rsw 结果集包装类
* @param rowBounds 分页对象
*/
public DefaultCursor(DefaultResultSetHandler resultSetHandler, ResultMap resultMap, ResultSetWrapper rsw, RowBounds rowBounds) {
this.resultSetHandler = resultSetHandler;
this.resultMap = resultMap;
this.rsw = rsw;
this.rowBounds = rowBounds;
}

/**
* 判断游标是否被打开
* @return
*/
@Override
public boolean isOpen() {
return status == CursorStatus.OPEN;
}

/**
* 判断游标是否被消费掉
* @return
*/
@Override
public boolean isConsumed() {
return status == CursorStatus.CONSUMED;
}

/**
* 获取当前的索引
* 分页偏移量 + 游标迭代器索引位置
* @return
*/
@Override
public int getCurrentIndex() {
return rowBounds.getOffset() + cursorIterator.iteratorIndex;
}

/**
* 获取迭代器
* @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;
}

/**
* 关闭游标
*/
@Override
public void close() {
if (isClosed()) {
return;
}

ResultSet rs = rsw.getResultSet();
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
// ignore
} finally {
status = CursorStatus.CLOSED;
}
}

protected T fetchNextUsingRowBound() {
T result = fetchNextObjectFromDatabase();
// 过滤到偏移量的位置
while (objectWrapperResultHandler.fetched && indexWithRowBound < rowBounds.getOffset()) {
result = fetchNextObjectFromDatabase();
}
return result;
}

/**
* 从数据库中获取下一个对象
* @return
*/
protected T fetchNextObjectFromDatabase() {
// 判断游标是否已经关闭,已关闭返回null
if (isClosed()) {
return null;
}
try {
objectWrapperResultHandler.fetched = false;
// 设置当前状态是游标打开状态
status = CursorStatus.OPEN;
// 如果结果集包装类不是已经关闭
// 把结果放入objectWrapperResultHandler对象的result中
if (!rsw.getResultSet().isClosed()) {
resultSetHandler.handleRowValues(rsw, resultMap, objectWrapperResultHandler, RowBounds.DEFAULT, null);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}

// 获取对象包装处理的结果
// 如果结果不为空结果, 索引++
T next = objectWrapperResultHandler.result;
if (objectWrapperResultHandler.fetched) {
indexWithRowBound++;
}
// No more object or limit reached
// next为null或者读取条数等于偏移量+限制条数
if (!objectWrapperResultHandler.fetched || getReadItemsCount() == rowBounds.getOffset() + rowBounds.getLimit()) {
close();
status = CursorStatus.CONSUMED;
}
// 把结果设置为null
objectWrapperResultHandler.result = null;

return next;
}

/**
* 判断是否关闭
* 游标本身处于关闭状态,或者已经取出结果的所有元素
* @return
*/
private boolean isClosed() {
return status == CursorStatus.CLOSED || status == CursorStatus.CONSUMED;
}

/**
* 下一个读取索引位置
* @return
*/
private int getReadItemsCount() {
return indexWithRowBound + 1;
}

/**
* 对象结果集包装类
* @param <T>
*/
protected static class ObjectWrapperResultHandler<T> implements ResultHandler<T> {

/** 结果集 */
protected T result;
protected boolean fetched;

/**
* 下文中获取结果集
* @param context
*/
@Override
public void handleResult(ResultContext<? extends T> context) {
this.result = context.getResultObject();
context.stop();
fetched = true;
}
}

/**
* 游标迭代器对象
*/
protected class CursorIterator implements Iterator<T> {

/**
* 保存下一个将会被返回的对象
* Holder for the next object to be returned.
*/
T object;

/**
* 返回下一个对象的索引
* Index of objects returned using next(), and as such, visible to users.
*/
int iteratorIndex = -1;

/**
* 是否有下个
* @return
*/
@Override
public boolean hasNext() {
if (!objectWrapperResultHandler.fetched) {
object = fetchNextUsingRowBound();
}
return objectWrapperResultHandler.fetched;
}

@Override
public T next() {
// Fill next with object fetched from hasNext()
// 接下来填充从hasNext()获取的对象
T next = object;

// 未执行过hasNext
if (!objectWrapperResultHandler.fetched) {
next = fetchNextUsingRowBound();
}
// 执行过 hasNext
if (objectWrapperResultHandler.fetched) {
objectWrapperResultHandler.fetched = false;
object = null;
iteratorIndex++;
return next;
}
throw new NoSuchElementException();
}

@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove element from Cursor");
}
}
}