Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
</parent>

<artifactId>mybatis</artifactId>
<version>3.3.1-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>mybatis</name>
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/org/apache/ibatis/binding/MapperMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.apache.ibatis.annotations.Flush;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.MetaObject;
Expand Down Expand Up @@ -64,6 +65,8 @@ public Object execute(SqlSession sqlSession, Object[] args) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
Expand Down Expand Up @@ -132,6 +135,18 @@ private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
return result;
}

private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
} else {
result = sqlSession.<T>selectCursor(command.getName(), param);
}
return result;
}

private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
Expand Down Expand Up @@ -218,6 +233,7 @@ public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
Expand All @@ -229,6 +245,7 @@ public MethodSignature(Configuration configuration, Method method) {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.hasNamedParameters = hasNamedParams(method);
Expand Down Expand Up @@ -295,6 +312,10 @@ public boolean returnsVoid() {
return returnsVoid;
}

public boolean returnsCursor() {
return returnsCursor;
}

private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/org/apache/ibatis/cursor/Cursor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.cursor;

import java.io.Closeable;

/**
* 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.
* 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> {

/**
* @return true if the cursor has started to fetch items from database.
*/
boolean isOpen();

/**
*
* @return true if the cursor is fully consumed and has returned all elements matching the query.
*/
boolean isConsumed();

/**
* Get the current item index. The first item has the index 0.
* @return -1 if the cursor is not open and has not been consumed. The index of the current item retrieved.
*/
int getCurrentIndex();
}
189 changes: 189 additions & 0 deletions src/main/java/org/apache/ibatis/cursor/defaults/DefaultCursor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.cursor.defaults;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
import org.apache.ibatis.executor.resultset.ResultSetWrapper;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
* @author Guillaume Darmont / guillaume@dropinocean.com
*/
public class DefaultCursor<T> implements Cursor<T> {

// ResultSetHandler stuff
private final DefaultResultSetHandler resultSetHandler;
private final ResultMap resultMap;
private final ResultSetWrapper rsw;
private final RowBounds rowBounds;
private final ObjectWrapperResultHandler<T> objectWrapperResultHandler = new ObjectWrapperResultHandler<T>();

private int currentIndex = -1;

private boolean iteratorAlreadyOpened = false;

private boolean opened = false;

private boolean resultSetConsumed = false;

public DefaultCursor(DefaultResultSetHandler resultSetHandler, ResultMap resultMap, ResultSetWrapper rsw, RowBounds rowBounds) {
this.resultSetHandler = resultSetHandler;
this.resultMap = resultMap;
this.rsw = rsw;
this.rowBounds = rowBounds;
}

@Override
public boolean isOpen() {
return opened;
}

@Override
public boolean isConsumed() {
return resultSetConsumed;
}

@Override
public int getCurrentIndex() {
return currentIndex;
}

@Override
public Iterator<T> iterator() {
return new CursorIterator();
}

@Override
public void close() {
ResultSet rs = rsw.getResultSet();
try {
if (rs != null) {
Statement statement = rs.getStatement();

rs.close();
if (statement != null) {
statement.close();
}
}
opened = false;
} catch (SQLException e) {
// ignore
}
}

protected T fetchNextUsingRowBound() {
T result = fetchNextObjectFromDatabase();
while (currentIndex < rowBounds.getOffset()) {
result = fetchNextObjectFromDatabase();
}
return result;
}

protected T fetchNextObjectFromDatabase() {
if (resultSetConsumed) {
return null;
}

try {
opened = true;
resultSetHandler.handleRowValues(rsw, resultMap, objectWrapperResultHandler, RowBounds.DEFAULT, null);
} catch (SQLException e) {
throw new RuntimeException(e);
}

T next = objectWrapperResultHandler.result;
if (next != null) {
currentIndex++;
}
// No more object or limit reached
if (next == null || (getReadItemsCount() == rowBounds.getOffset() + rowBounds.getLimit())) {
close();
resultSetConsumed = true;
}
objectWrapperResultHandler.result = null;

return next;
}

private int getReadItemsCount() {
return currentIndex + 1;
}

private static class ObjectWrapperResultHandler<E> implements ResultHandler {

private E result;

@Override
public void handleResult(ResultContext context) {
this.result = (E) context.getResultObject();
context.stop();
}
}

private class CursorIterator implements Iterator<T> {

/**
* Holder for the next objet to be returned
*/
T object;

public CursorIterator() {
if (iteratorAlreadyOpened) {
throw new IllegalStateException("Cannot open more than one iterator on a Cursor");
}
iteratorAlreadyOpened = true;
}

@Override
public boolean hasNext() {
if (object == null) {
object = fetchNextUsingRowBound();
}
return object != null;
}

@Override
public T next() {
// Fill next with object fetched from hasNext()
T next = object;

if (next == null) {
next = fetchNextUsingRowBound();
}

if (next != null) {
object = null;
return next;
}
throw new NoSuchElementException();
}

@Override
public void remove() {
throw new UnsupportedOperationException("Cannot currently remove element from Cursor");
}
}
}
13 changes: 12 additions & 1 deletion src/main/java/org/apache/ibatis/executor/BaseExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
Expand Down Expand Up @@ -170,6 +172,12 @@ public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBoun
return list;
}

@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return doQueryCursor(ms, parameter, rowBounds, boundSql);
}

@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
if (closed) {
Expand Down Expand Up @@ -219,7 +227,7 @@ public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBo
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
}

@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
Expand Down Expand Up @@ -269,6 +277,9 @@ protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;

protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;

protected void closeStatement(Statement statement) {
if (statement != null) {
try {
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/apache/ibatis/executor/BatchExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
Expand Down Expand Up @@ -94,6 +95,17 @@ public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds
}
}

@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Connection connection = getConnection(ms.getStatementLog());
Statement stmt = handler.prepare(connection);
handler.parameterize(stmt);
return handler.<E>queryCursor(stmt);
}

@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/apache/ibatis/executor/CachingExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.TransactionalCacheManager;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
Expand Down Expand Up @@ -82,6 +83,12 @@ public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds r
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
flushCacheIfRequired(ms);
return delegate.queryCursor(ms, parameter, rowBounds);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Expand Down
Loading