介绍下是什么
- 这两个都可以用于流式查询,简单说下,流式查询可以在查询大量数据时候,防止内存爆掉。 简单点说,一次查100W条数据,JVM堆栈设置100M,如果使用流式查询,是不会出现内存溢出的。这两个的具体用法可以参考其他博客,这里就不重复垃圾话了。主要是说说这两有什么区别。
区别是什么?
- Cursor和ResultHandler功能是一样的,Cursor比ResultHandler多包装了一层,实际Cursor调用时候,还是和ResultHandler调用的逻辑一样。具体我们看源码:
ResultHandler的逻辑
- 判断走哪个逻辑查询
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
//查询ResultHandler会走这个逻辑
"executeWithResultHandler(sqlSession, args);"
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//以下代码省略
- 下一步调用这个方法
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//查询数据库获取游标数据的方法
ps.execute();
//进入handleResultSets这个方法
return resultSetHandler.handleResultSets(ps);
}
- 一路往下,最终会调用这个方法
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
//主要看这里,这个循环,看见resultSet.next()这个方法没有,是不是很熟悉?原生调用JDBC查询数据时候,会有这个一个方法返回,每次next表示查看下一个游标对应的数据还有没有,如果有就取出来,没有就是结束,查完了。
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
- 具体的执行方法在这里
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
//resultContext是存放数据的上下文对象
resultContext.nextResultObject(rowValue);
//这个是在哪个方法里取出上下文数据做处理的方法,有点拗口。
//其实就是你用ResultHandler查询时候,要写一个实现类做数据处理
//这个实现类就是在这个时候被调用的,通过这个方法调用,数据在哪里?
//看见resultContext没有,这个类里就装了你的数据,并且是一条条数据的做处理。
//处理完后,会被后一条数据替换。这个就是流式查询最核心的地方。
//我们平时用得最多的list查询,其实只是把查询结果装在一个list里而已。有兴趣的老铁可以打断点自己看看
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
Cursor的逻辑:
- 判断走哪个逻辑查询
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()) {
//查询Cursor会走这个逻辑
"result = executeForCursor(sqlSession, args);"
}
//以下代码省略
- 这个方法是主要执行的方法
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//去数据库查询所有游标数据,得到的数据放在ps上
ps.execute();
//传入ps,执行下面这个方法
return resultSetHandler.handleCursorResultSets(ps);
}
下一步,执行这个方法
@Override
public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
if (resultMapCount != 1) {
throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
}
ResultMap resultMap = resultMaps.get(0);
//主要看这里,返回值中,创建了Cursor接口的实现类,并传入对应数据,这些数据肯定会通过构造器赋值给这个类里的属性。
return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
}
- 下一步。
//这个是Cursor实现类的构造器
public DefaultCursor(DefaultResultSetHandler resultSetHandler, ResultMap resultMap, ResultSetWrapper rsw, RowBounds rowBounds) {
//主要看这个属性,resultSetHandler,把屏幕拉到最上面,找到属性名resultSetHandler
this.resultSetHandler = resultSetHandler;
this.resultMap = resultMap;
this.rsw = rsw;
this.rowBounds = rowBounds;
}
- 关键还是这三个属性
//这个是不是很眼熟,最终还是调用这个属性resultSetHandler里的方法处理数据
private final DefaultResultSetHandler resultSetHandler;
//当前类DefaultCursor可以写泛型,那么泛型类对应的属性就是objectWrapperResultHandler,也就是你PO的对象(也可能是DTO等)
protected final ObjectWrapperResultHandler<T> objectWrapperResultHandler = new ObjectWrapperResultHandler<>();
//迭代器,是一个内部类,本类下面有方法,把获取的数据放进objectWrapperResultHandler里。
private final CursorIterator cursorIterator = new CursorIterator();
- 所以最终调用获取数据的方法还是这个方法。和ResultHandler没太大区别,所以个人认为Cursor只是多包装了一层而已。
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
resultContext.nextResultObject(rowValue);
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
总结:
- 在使用的时候,Cursor需要自己关闭连接,具体怎么执行,可查看其他博客
- ResultHandler使用时要注意,fetchSize字段的值根据需要设置,如果一次查询的数据量没超过堆栈上限,可以设置100-1000之间的值(具体看业务)提高查询效率,这个值的意思是,每次查询从数据库一次取多少条数据,然后存放在内存里,然后在通过从内存拿这个数据。
- 如果要实现查询百万以上数据,还不想被爆内存可以把fetchSize设置为Integer.MIN_VALUE,具体为什么,我也没搞清楚,因为涉及要去数据库底层看逻辑,能力有限,暂时没搞明白。
- Cursor要实现怎么查询百万以上的数据,目前也没搞明白。
- 能力有限,写得不好的地方希望指出,感谢观看的老铁们。如果有大佬路过,希望能指点下,感谢。