mybatis源码解读:cursor包
欢迎关注本人公众号:
1.游标的使用
在使用mybatis进行数据库查询时,经常会查询到大量的结果。游标可以解决处理大量数据时不是一次读入整个结果集,而是逐一读入和处理结果,这样可以减少对内存的占用。
在mybatis中使用游标进行查询非常简单,映射文件不需要任何的变动,只需要在映射接口中标明返回值类型是Cursor。
Cursor<User> selectAll();
2.游标接口
cursor包中源码非常简单,只有一个cursor接口和默认的实现类DefaultCursor。
Cursor类接口继承了Closeable接口和Iterable接口。Closeable接口表明一个类是可以关闭的,调用close方法可以释放类对象持有的资源。Iterable接口表明一个类是可以迭代的, 这样可以对类对象使用for-each操作。
public interface Cursor<T> extends Closeable, Iterable<T> {
/** * 游标是否开启 * @return 是否开启 */ boolean isOpen(); /** * 是否已经完成了所有遍历 * @return 是否完成了所有遍历 */ boolean isConsumed(); /** * 返回当前元素的索引 * @return 当前元素的索引 */ int getCurrentIndex();}
4.默认游标
DefaultCursor类是默认的游标,有3个内部类。
1.CursorStatus内部类非常简单,是一个表明游标状态的枚举类。
private enum CursorStatus {
CREATED, // 表征游标新创建,结果集尚未消费 OPEN, // 表征游标正在被使用中,结果集正在被消费 CLOSED, // 表征游标已经被关闭,但其中的结果集未被完全消费 CONSUMED // 表征游标已经被关闭,其中的结果集已经被完全消费 }
2.ObjectWrapperResultHandler类继承ResultHandler接口,是一个简单结果处理器。
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内部类只是将结果上下文中的一条结果取出来放进自身的属性,并未做进一步处理。
3.CursorIterator内部类继承了Iterator接口,这是一个迭代器类,实现了判断是否存在下一个元素的hashNext方法和返回下一个元素的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"); } }
4.DefaultCursor类中大多数方法是用来实现Cursor、Closeable、Iterable三个接口的方法。其中Iterable接口中定义的iterator方法使用iteratorRetrieved变量保证了迭代器只能给出一次,防止多次给出造成的访问混乱。
public class DefaultCursor<T> implements Cursor<T> {
// 结果集处理器 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; /** * 返回迭代器 * @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; } /** * 考虑边界限制(翻页限制),从数据库中获取下一个对象 * @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方法在每次调用时都会从数据库查询返回的结果集中取出一条结果,而fetchNextUsingRowBound方法则在此基础上考虑了查询时的边界限制条件。这2个方法共同完成了在满足边界限制的情况下, 每次从结果集中取出了一条结果的功能。所以对于DefaultCursor类,结果集中的所有记录都已经存储在内存中了,DefaultCursor类只负责逐一给出这些记录而已。
还没有评论,来说两句吧...