diff --git a/src/main/java/org/apache/ibatis/cursor/defaults/DefaultCursor.java b/src/main/java/org/apache/ibatis/cursor/defaults/DefaultCursor.java index e82a0d09241..33c1f797e8d 100644 --- a/src/main/java/org/apache/ibatis/cursor/defaults/DefaultCursor.java +++ b/src/main/java/org/apache/ibatis/cursor/defaults/DefaultCursor.java @@ -37,188 +37,190 @@ */ public class DefaultCursor implements Cursor { - // ResultSetHandler stuff - private final DefaultResultSetHandler resultSetHandler; - private final ResultMap resultMap; - private final ResultSetWrapper rsw; - private final RowBounds rowBounds; - private final ObjectWrapperResultHandler objectWrapperResultHandler = new ObjectWrapperResultHandler<>(); - - private final CursorIterator cursorIterator = new CursorIterator(); - private boolean iteratorRetrieved; - - private CursorStatus status = CursorStatus.CREATED; - private int indexWithRowBound = -1; - - 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 + // ResultSetHandler stuff + private final DefaultResultSetHandler resultSetHandler; + private final ResultMap resultMap; + private final ResultSetWrapper rsw; + private final RowBounds rowBounds; + private final ObjectWrapperResultHandler objectWrapperResultHandler = new ObjectWrapperResultHandler<>(); + + private final CursorIterator cursorIterator = new CursorIterator(); + private boolean iteratorRetrieved; + + private CursorStatus status = CursorStatus.CREATED; + private int indexWithRowBound = -1; + + 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 + } + + 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 status == CursorStatus.OPEN; + } + + @Override + public boolean isConsumed() { + return status == CursorStatus.CONSUMED; + } + + @Override + public int getCurrentIndex() { + return rowBounds.getOffset() + cursorIterator.iteratorIndex; + } + + @Override + public Iterator iterator() { + if (iteratorRetrieved) { + throw new IllegalStateException("Cannot open more than one iterator on a Cursor"); } - - public DefaultCursor(DefaultResultSetHandler resultSetHandler, ResultMap resultMap, ResultSetWrapper rsw, RowBounds rowBounds) { - this.resultSetHandler = resultSetHandler; - this.resultMap = resultMap; - this.rsw = rsw; - this.rowBounds = rowBounds; + if (isClosed()) { + throw new IllegalStateException("A Cursor is already closed."); } - - @Override - public boolean isOpen() { - return status == CursorStatus.OPEN; + iteratorRetrieved = true; + return cursorIterator; + } + + @Override + public void close() { + if (isClosed()) { + return; } - @Override - public boolean isConsumed() { - return status == CursorStatus.CONSUMED; - } + ResultSet rs = rsw.getResultSet(); + try { + if (rs != null) { + Statement statement = rs.getStatement(); - @Override - public int getCurrentIndex() { - return rowBounds.getOffset() + cursorIterator.iteratorIndex; + rs.close(); + if (statement != null) { + statement.close(); + } + } + status = CursorStatus.CLOSED; + } catch (SQLException e) { + // ignore } + } - @Override - public Iterator 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; + protected T fetchNextUsingRowBound() { + T result = fetchNextObjectFromDatabase(); + while (result != null && indexWithRowBound < rowBounds.getOffset()) { + result = fetchNextObjectFromDatabase(); } + return result; + } - @Override - public void close() { - if (isClosed()) { - return; - } + protected T fetchNextObjectFromDatabase() { + if (isClosed()) { + return null; + } - ResultSet rs = rsw.getResultSet(); - try { - if (rs != null) { - Statement statement = rs.getStatement(); - - rs.close(); - if (statement != null) { - statement.close(); - } - } - status = CursorStatus.CLOSED; - } catch (SQLException e) { - // ignore - } + try { + status = CursorStatus.OPEN; + if (!rsw.getResultSet().isClosed()) { + resultSetHandler.handleRowValues(rsw, resultMap, objectWrapperResultHandler, RowBounds.DEFAULT, null); + } + } catch (SQLException e) { + throw new RuntimeException(e); } - protected T fetchNextUsingRowBound() { - T result = fetchNextObjectFromDatabase(); - while (result != null && indexWithRowBound < rowBounds.getOffset()) { - result = fetchNextObjectFromDatabase(); - } - return result; + T next = objectWrapperResultHandler.result; + if (next != null) { + indexWithRowBound++; + } + // No more object or limit reached + if (next == null || getReadItemsCount() == rowBounds.getOffset() + rowBounds.getLimit()) { + close(); + status = CursorStatus.CONSUMED; } + objectWrapperResultHandler.result = null; - protected T fetchNextObjectFromDatabase() { - if (isClosed()) { - return null; - } + return next; + } - try { - status = CursorStatus.OPEN; - resultSetHandler.handleRowValues(rsw, resultMap, objectWrapperResultHandler, RowBounds.DEFAULT, null); - } catch (SQLException e) { - throw new RuntimeException(e); - } + private boolean isClosed() { + return status == CursorStatus.CLOSED || status == CursorStatus.CONSUMED; + } - T next = objectWrapperResultHandler.result; - if (next != null) { - indexWithRowBound++; - } - // No more object or limit reached - if (next == null || getReadItemsCount() == rowBounds.getOffset() + rowBounds.getLimit()) { - close(); - status = CursorStatus.CONSUMED; - } - objectWrapperResultHandler.result = null; + private int getReadItemsCount() { + return indexWithRowBound + 1; + } - return next; - } + private static class ObjectWrapperResultHandler implements ResultHandler { - private boolean isClosed() { - return status == CursorStatus.CLOSED || status == CursorStatus.CONSUMED; - } + private T result; - private int getReadItemsCount() { - return indexWithRowBound + 1; + @Override + public void handleResult(ResultContext context) { + this.result = context.getResultObject(); + context.stop(); } + } - private static class ObjectWrapperResultHandler implements ResultHandler { - - private T result; + private class CursorIterator implements Iterator { - @Override - public void handleResult(ResultContext context) { - this.result = context.getResultObject(); - context.stop(); - } - } + /** + * Holder for the next object to be returned + */ + T object; - private class CursorIterator implements Iterator { + /** + * Index of objects returned using next(), and as such, visible to users. + */ + int iteratorIndex = -1; - /** - * Holder for the next object to be returned - */ - T object; + @Override + public boolean hasNext() { + if (object == null) { + object = fetchNextUsingRowBound(); + } + return object != null; + } - /** - * Index of objects returned using next(), and as such, visible to users. - */ - int iteratorIndex = -1; + @Override + public T next() { + // Fill next with object fetched from hasNext() + T next = object; - @Override - public boolean hasNext() { - if (object == null) { - object = fetchNextUsingRowBound(); - } - return object != null; - } + if (next == null) { + next = fetchNextUsingRowBound(); + } - @Override - public T next() { - // Fill next with object fetched from hasNext() - T next = object; - - if (next == null) { - next = fetchNextUsingRowBound(); - } - - if (next != null) { - object = null; - iteratorIndex++; - return next; - } - throw new NoSuchElementException(); - } + if (next != null) { + object = null; + iteratorIndex++; + return next; + } + throw new NoSuchElementException(); + } - @Override - public void remove() { - throw new UnsupportedOperationException("Cannot remove element from Cursor"); - } + @Override + public void remove() { + throw new UnsupportedOperationException("Cannot remove element from Cursor"); } + } } diff --git a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java index 92cb2598bd8..f43eefa1035 100644 --- a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java +++ b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java @@ -347,11 +347,12 @@ protected void checkResultHandler() { private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext resultContext = new DefaultResultContext<>(); - skipRows(rsw.getResultSet(), rowBounds); - while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { - ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); + ResultSet resultSet = rsw.getResultSet(); + skipRows(resultSet, rowBounds); + 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, rsw.getResultSet()); + storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } } @@ -380,7 +381,9 @@ private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException { } } else { for (int i = 0; i < rowBounds.getOffset(); i++) { - rs.next(); + if (!rs.next()) { + break; + } } } } @@ -846,28 +849,29 @@ private String prependPrefix(String columnName, String prefix) { private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { final DefaultResultContext resultContext = new DefaultResultContext<>(); - skipRows(rsw.getResultSet(), rowBounds); + ResultSet resultSet = rsw.getResultSet(); + skipRows(resultSet, rowBounds); Object rowValue = previousRowValue; - while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { - final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); + while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { + final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); Object partialObject = nestedResultObjects.get(rowKey); // issue #577 && #542 if (mappedStatement.isResultOrdered()) { if (partialObject == null && rowValue != null) { nestedResultObjects.clear(); - storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); + storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); } else { rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); if (partialObject == null) { - storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); + storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } } } if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) { - storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); + storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); previousRowValue = null; } else if (rowValue != null) { previousRowValue = rowValue; diff --git a/src/test/java/org/apache/ibatis/cursor/defaults/DefaultCursorTest.java b/src/test/java/org/apache/ibatis/cursor/defaults/DefaultCursorTest.java new file mode 100644 index 00000000000..7f6862b1cbe --- /dev/null +++ b/src/test/java/org/apache/ibatis/cursor/defaults/DefaultCursorTest.java @@ -0,0 +1,204 @@ +/** + * Copyright 2009-2018 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 static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.builder.StaticSqlSource; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.resultset.DefaultResultSetHandler; +import org.apache.ibatis.executor.resultset.ResultSetWrapper; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.apache.ibatis.type.TypeHandlerRegistry; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class DefaultCursorTest { + @Spy + private ImpatientResultSet rs; + @Mock + protected ResultSetMetaData rsmd; + @Mock + private Connection conn; + @Mock + private DatabaseMetaData dbmd; + @Mock + private Statement stmt; + + @SuppressWarnings("unchecked") + @Test + public void shouldCloseImmediatelyIfResultSetIsClosed() throws Exception { + final MappedStatement ms = getNestedAndOrderedMappedStatement(); + final ResultMap rm = ms.getResultMaps().get(0); + + final Executor executor = null; + final ParameterHandler parameterHandler = null; + final ResultHandler resultHandler = null; + final BoundSql boundSql = null; + final RowBounds rowBounds = RowBounds.DEFAULT; + + final DefaultResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, ms, parameterHandler, + resultHandler, boundSql, rowBounds); + + when(stmt.getResultSet()).thenReturn(rs); + when(rsmd.getColumnCount()).thenReturn(2); + when(rsmd.getColumnLabel(1)).thenReturn("id"); + when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER); + when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName()); + when(rsmd.getColumnLabel(2)).thenReturn("role"); + when(rsmd.getColumnType(2)).thenReturn(Types.VARCHAR); + when(rsmd.getColumnClassName(2)).thenReturn(String.class.getCanonicalName()); + when(stmt.getConnection()).thenReturn(conn); + when(conn.getMetaData()).thenReturn(dbmd); + when(dbmd.supportsMultipleResultSets()).thenReturn(false); + + final ResultSetWrapper rsw = new ResultSetWrapper(rs, ms.getConfiguration()); + + try (DefaultCursor cursor = new DefaultCursor<>(resultSetHandler, rm, rsw, RowBounds.DEFAULT)) { + Iterator iter = cursor.iterator(); + assertTrue(iter.hasNext()); + Map map = (Map) iter.next(); + assertEquals(Integer.valueOf(1), map.get("id")); + assertEquals("CEO", ((Map) map.get("roles")).get("role")); + + assertFalse(cursor.isConsumed()); + assertTrue(cursor.isOpen()); + + assertFalse(iter.hasNext()); + assertTrue(cursor.isConsumed()); + assertFalse(cursor.isOpen()); + } + } + + @SuppressWarnings("serial") + private MappedStatement getNestedAndOrderedMappedStatement() { + final Configuration config = new Configuration(); + final TypeHandlerRegistry registry = config.getTypeHandlerRegistry(); + + ResultMap nestedResultMap = new ResultMap.Builder(config, "roleMap", HashMap.class, + new ArrayList() { + { + add(new ResultMapping.Builder(config, "role", "role", registry.getTypeHandler(String.class)) + .build()); + } + }).build(); + config.addResultMap(nestedResultMap); + + return new MappedStatement.Builder(config, "selectPerson", new StaticSqlSource(config, "select person..."), + SqlCommandType.SELECT).resultMaps( + new ArrayList() { + { + add(new ResultMap.Builder(config, "personMap", HashMap.class, new ArrayList() { + { + add(new ResultMapping.Builder(config, "id", "id", registry.getTypeHandler(Integer.class)) + .build()); + add(new ResultMapping.Builder(config, "roles").nestedResultMapId("roleMap").build()); + } + }).build()); + } + }) + .resultOrdered(true) + .build(); + } + + /* + * Simulate a driver that closes ResultSet automatically when next() returns false (e.g. DB2). + */ + protected abstract class ImpatientResultSet implements ResultSet { + private int rowIndex = -1; + private List> rows = new ArrayList<>(); + + protected ImpatientResultSet() { + Map row = new HashMap<>(); + row.put("id", Integer.valueOf(1)); + row.put("role", "CEO"); + rows.add(row); + } + + @Override + public boolean next() throws SQLException { + throwIfClosed(); + return ++rowIndex < rows.size(); + } + + @Override + public boolean isClosed() throws SQLException { + return rowIndex >= rows.size(); + } + + @Override + public String getString(String columnLabel) throws SQLException { + throwIfClosed(); + return (String) rows.get(rowIndex).get(columnLabel); + } + + @Override + public int getInt(String columnLabel) throws SQLException { + throwIfClosed(); + return (Integer) rows.get(rowIndex).get(columnLabel); + } + + @Override + public boolean wasNull() throws SQLException { + throwIfClosed(); + return false; + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return rsmd; + } + + @Override + public int getType() throws SQLException { + throwIfClosed(); + return ResultSet.TYPE_FORWARD_ONLY; + } + + private void throwIfClosed() throws SQLException { + if (rowIndex >= rows.size()) { + throw new SQLException("Invalid operation: result set is closed."); + } + } + } +} diff --git a/src/test/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandlerTest2.java b/src/test/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandlerTest2.java new file mode 100644 index 00000000000..95d25c2f018 --- /dev/null +++ b/src/test/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandlerTest2.java @@ -0,0 +1,217 @@ +/** + * Copyright 2009-2018 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.executor.resultset; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.builder.StaticSqlSource; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.apache.ibatis.type.TypeHandlerRegistry; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class DefaultResultSetHandlerTest2 { + + @Spy + private ImpatientResultSet rs; + @Mock + private Statement stmt; + @Mock + protected ResultSetMetaData rsmd; + @Mock + private Connection conn; + @Mock + private DatabaseMetaData dbmd; + + @SuppressWarnings("serial") + @Test + public void shouldNotCallNextOnClosedResultSet_SimpleResult() throws Exception { + final Configuration config = new Configuration(); + final TypeHandlerRegistry registry = config.getTypeHandlerRegistry(); + final MappedStatement ms = new MappedStatement.Builder(config, "testSelect", + new StaticSqlSource(config, "some select statement"), SqlCommandType.SELECT).resultMaps( + new ArrayList() { + { + add(new ResultMap.Builder(config, "testMap", HashMap.class, new ArrayList() { + { + add(new ResultMapping.Builder(config, "id", "id", registry.getTypeHandler(Integer.class)).build()); + } + }).build()); + } + }).build(); + + final Executor executor = null; + final ParameterHandler parameterHandler = null; + final ResultHandler resultHandler = null; + final BoundSql boundSql = null; + final RowBounds rowBounds = new RowBounds(5, 1); + final DefaultResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, ms, parameterHandler, + resultHandler, boundSql, rowBounds); + + when(stmt.getResultSet()).thenReturn(rs); + when(rsmd.getColumnCount()).thenReturn(1); + when(rsmd.getColumnLabel(1)).thenReturn("id"); + when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER); + when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName()); + when(stmt.getConnection()).thenReturn(conn); + when(conn.getMetaData()).thenReturn(dbmd); + when(dbmd.supportsMultipleResultSets()).thenReturn(false); // for simplicity. + + final List results = resultSetHandler.handleResultSets(stmt); + assertEquals(0, results.size()); + } + + @SuppressWarnings("serial") + @Test + public void shouldNotCallNextOnClosedResultSet_NestedResult() throws Exception { + final Configuration config = new Configuration(); + final TypeHandlerRegistry registry = config.getTypeHandlerRegistry(); + final ResultMap nestedResultMap = new ResultMap.Builder(config, "roleMap", HashMap.class, + new ArrayList() { + { + add(new ResultMapping.Builder(config, "role", "role", registry.getTypeHandler(String.class)) + .build()); + } + }).build(); + config.addResultMap(nestedResultMap); + final MappedStatement ms = new MappedStatement.Builder(config, "selectPerson", + new StaticSqlSource(config, "select person..."), + SqlCommandType.SELECT).resultMaps( + new ArrayList() { + { + add(new ResultMap.Builder(config, "personMap", HashMap.class, new ArrayList() { + { + add(new ResultMapping.Builder(config, "id", "id", registry.getTypeHandler(Integer.class)) + .build()); + add(new ResultMapping.Builder(config, "roles").nestedResultMapId("roleMap").build()); + } + }).build()); + } + }) + .resultOrdered(true) + .build(); + + final Executor executor = null; + final ParameterHandler parameterHandler = null; + final ResultHandler resultHandler = null; + final BoundSql boundSql = null; + final RowBounds rowBounds = new RowBounds(5, 1); + final DefaultResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, ms, parameterHandler, + resultHandler, boundSql, rowBounds); + + when(stmt.getResultSet()).thenReturn(rs); + when(rsmd.getColumnCount()).thenReturn(2); + when(rsmd.getColumnLabel(1)).thenReturn("id"); + when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER); + when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName()); + when(rsmd.getColumnLabel(2)).thenReturn("role"); + when(rsmd.getColumnType(2)).thenReturn(Types.VARCHAR); + when(rsmd.getColumnClassName(2)).thenReturn(String.class.getCanonicalName()); + when(stmt.getConnection()).thenReturn(conn); + when(conn.getMetaData()).thenReturn(dbmd); + when(dbmd.supportsMultipleResultSets()).thenReturn(false); // for simplicity. + + final List results = resultSetHandler.handleResultSets(stmt); + assertEquals(0, results.size()); + } + + /* + * Simulate a driver that closes ResultSet automatically when next() returns false (e.g. DB2). + */ + protected abstract class ImpatientResultSet implements ResultSet { + private int rowIndex = -1; + private List> rows = new ArrayList<>(); + + protected ImpatientResultSet() { + Map row = new HashMap<>(); + row.put("id", Integer.valueOf(1)); + row.put("role", "CEO"); + rows.add(row); + } + + @Override + public boolean next() throws SQLException { + throwIfClosed(); + return ++rowIndex < rows.size(); + } + + @Override + public boolean isClosed() throws SQLException { + return rowIndex >= rows.size(); + } + + @Override + public String getString(String columnLabel) throws SQLException { + throwIfClosed(); + return (String) rows.get(rowIndex).get(columnLabel); + } + + @Override + public int getInt(String columnLabel) throws SQLException { + throwIfClosed(); + return (Integer) rows.get(rowIndex).get(columnLabel); + } + + @Override + public boolean wasNull() throws SQLException { + throwIfClosed(); + return false; + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return rsmd; + } + + @Override + public int getType() throws SQLException { + throwIfClosed(); + return ResultSet.TYPE_FORWARD_ONLY; + } + + private void throwIfClosed() throws SQLException { + if (rowIndex >= rows.size()) { + throw new SQLException("Invalid operation: result set is closed."); + } + } + } +} diff --git a/src/test/java/org/apache/ibatis/submitted/cursor_nested/CursorNestedTest.java b/src/test/java/org/apache/ibatis/submitted/cursor_nested/CursorNestedTest.java index 02c3f814622..7787b2858aa 100644 --- a/src/test/java/org/apache/ibatis/submitted/cursor_nested/CursorNestedTest.java +++ b/src/test/java/org/apache/ibatis/submitted/cursor_nested/CursorNestedTest.java @@ -31,75 +31,78 @@ public class CursorNestedTest { - private static SqlSessionFactory sqlSessionFactory; - - @BeforeClass - public static void setUp() throws Exception { - // create a SqlSessionFactory - try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/cursor_nested/mybatis-config.xml")) { - sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); - } - - // populate in-memory database - BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), - "org/apache/ibatis/submitted/cursor_nested/CreateDB.sql"); + private static SqlSessionFactory sqlSessionFactory; + + @BeforeClass + public static void setUp() throws Exception { + // create a SqlSessionFactory + try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/cursor_nested/mybatis-config.xml")) { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } - @Test - public void shouldGetAllUser() { - Cursor usersCursor; - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - Mapper mapper = sqlSession.getMapper(Mapper.class); - usersCursor = mapper.getAllUsers(); - - Assert.assertFalse(usersCursor.isOpen()); - // Retrieving iterator, fetching is not started - Iterator iterator = usersCursor.iterator(); - - // Check if hasNext, fetching is started - Assert.assertTrue(iterator.hasNext()); - Assert.assertTrue(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); - - User user = iterator.next(); - Assert.assertEquals(2, user.getGroups().size()); - Assert.assertEquals(3, user.getRoles().size()); - - user = iterator.next(); - Assert.assertEquals(1, user.getGroups().size()); - Assert.assertEquals(3, user.getRoles().size()); - - user = iterator.next(); - Assert.assertEquals(3, user.getGroups().size()); - Assert.assertEquals(1, user.getRoles().size()); - - user = iterator.next(); - Assert.assertEquals(2, user.getGroups().size()); - Assert.assertEquals(2, user.getRoles().size()); - - // Check no more elements - Assert.assertFalse(iterator.hasNext()); - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertTrue(usersCursor.isConsumed()); - } - Assert.assertFalse(usersCursor.isOpen()); + // populate in-memory database + BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), + "org/apache/ibatis/submitted/cursor_nested/CreateDB.sql"); + } + + @Test + public void shouldGetAllUser() { + Cursor usersCursor; + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + usersCursor = mapper.getAllUsers(); + + Assert.assertFalse(usersCursor.isOpen()); + // Retrieving iterator, fetching is not started + Iterator iterator = usersCursor.iterator(); + + // Check if hasNext, fetching is started + Assert.assertTrue(iterator.hasNext()); + Assert.assertTrue(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); + + User user = iterator.next(); + Assert.assertEquals(2, user.getGroups().size()); + Assert.assertEquals(3, user.getRoles().size()); + + user = iterator.next(); + Assert.assertEquals(1, user.getGroups().size()); + Assert.assertEquals(3, user.getRoles().size()); + + user = iterator.next(); + Assert.assertEquals(3, user.getGroups().size()); + Assert.assertEquals(1, user.getRoles().size()); + + user = iterator.next(); + Assert.assertEquals(2, user.getGroups().size()); + Assert.assertEquals(2, user.getRoles().size()); + + Assert.assertTrue(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); + + // Check no more elements + Assert.assertFalse(iterator.hasNext()); + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertTrue(usersCursor.isConsumed()); } + Assert.assertFalse(usersCursor.isOpen()); + } - @Test - public void testCursorWithRowBound() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(2, 1)); + @Test + public void testCursorWithRowBound() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(2, 1)); - Iterator iterator = usersCursor.iterator(); + Iterator iterator = usersCursor.iterator(); - Assert.assertTrue(iterator.hasNext()); - User user = iterator.next(); - Assert.assertEquals("User3", user.getName()); - Assert.assertEquals(2, usersCursor.getCurrentIndex()); + Assert.assertTrue(iterator.hasNext()); + User user = iterator.next(); + Assert.assertEquals("User3", user.getName()); + Assert.assertEquals(2, usersCursor.getCurrentIndex()); - Assert.assertFalse(iterator.hasNext()); - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertTrue(usersCursor.isConsumed()); - } + Assert.assertFalse(iterator.hasNext()); + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertTrue(usersCursor.isConsumed()); } + } } diff --git a/src/test/java/org/apache/ibatis/submitted/cursor_simple/CursorSimpleTest.java b/src/test/java/org/apache/ibatis/submitted/cursor_simple/CursorSimpleTest.java index 66d68e9d111..8bda75628cd 100644 --- a/src/test/java/org/apache/ibatis/submitted/cursor_simple/CursorSimpleTest.java +++ b/src/test/java/org/apache/ibatis/submitted/cursor_simple/CursorSimpleTest.java @@ -35,357 +35,356 @@ public class CursorSimpleTest { - private static SqlSessionFactory sqlSessionFactory; - - @BeforeClass - public static void setUp() throws Exception { - // create a SqlSessionFactory - try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/cursor_simple/mybatis-config.xml")) { - sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); - } - - // populate in-memory database - BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), - "org/apache/ibatis/submitted/cursor_simple/CreateDB.sql"); - } + private static SqlSessionFactory sqlSessionFactory; - @Test - public void shouldGetAllUser() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - Mapper mapper = sqlSession.getMapper(Mapper.class); - Cursor usersCursor = mapper.getAllUsers(); + @BeforeClass + public static void setUp() throws Exception { + // create a SqlSessionFactory + try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/cursor_simple/mybatis-config.xml")) { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + } - Assert.assertFalse(usersCursor.isOpen()); + // populate in-memory database + BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), + "org/apache/ibatis/submitted/cursor_simple/CreateDB.sql"); + } - // Cursor is just created, current index is -1 - Assert.assertEquals(-1, usersCursor.getCurrentIndex()); + @Test + public void shouldGetAllUser() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + Cursor usersCursor = mapper.getAllUsers(); - Iterator iterator = usersCursor.iterator(); + Assert.assertFalse(usersCursor.isOpen()); - // Check if hasNext, fetching is started - Assert.assertTrue(iterator.hasNext()); - Assert.assertTrue(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); + // Cursor is just created, current index is -1 + Assert.assertEquals(-1, usersCursor.getCurrentIndex()); - // next() has not been called, index is still -1 - Assert.assertEquals(-1, usersCursor.getCurrentIndex()); + Iterator iterator = usersCursor.iterator(); - User user = iterator.next(); - Assert.assertEquals("User1", user.getName()); - Assert.assertEquals(0, usersCursor.getCurrentIndex()); + // Check if hasNext, fetching is started + Assert.assertTrue(iterator.hasNext()); + Assert.assertTrue(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); - user = iterator.next(); - Assert.assertEquals("User2", user.getName()); - Assert.assertEquals(1, usersCursor.getCurrentIndex()); + // next() has not been called, index is still -1 + Assert.assertEquals(-1, usersCursor.getCurrentIndex()); - user = iterator.next(); - Assert.assertEquals("User3", user.getName()); - Assert.assertEquals(2, usersCursor.getCurrentIndex()); + User user = iterator.next(); + Assert.assertEquals("User1", user.getName()); + Assert.assertEquals(0, usersCursor.getCurrentIndex()); - user = iterator.next(); - Assert.assertEquals("User4", user.getName()); - Assert.assertEquals(3, usersCursor.getCurrentIndex()); + user = iterator.next(); + Assert.assertEquals("User2", user.getName()); + Assert.assertEquals(1, usersCursor.getCurrentIndex()); - user = iterator.next(); - Assert.assertEquals("User5", user.getName()); - Assert.assertEquals(4, usersCursor.getCurrentIndex()); + user = iterator.next(); + Assert.assertEquals("User3", user.getName()); + Assert.assertEquals(2, usersCursor.getCurrentIndex()); - // Check no more elements - Assert.assertFalse(iterator.hasNext()); - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertTrue(usersCursor.isConsumed()); - } - } + user = iterator.next(); + Assert.assertEquals("User4", user.getName()); + Assert.assertEquals(3, usersCursor.getCurrentIndex()); - @Test - public void testCursorClosedOnSessionClose() { - Cursor usersCursor; - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - Mapper mapper = sqlSession.getMapper(Mapper.class); - usersCursor = mapper.getAllUsers(); + user = iterator.next(); + Assert.assertEquals("User5", user.getName()); + Assert.assertEquals(4, usersCursor.getCurrentIndex()); - Assert.assertFalse(usersCursor.isOpen()); + // Check no more elements + Assert.assertFalse(iterator.hasNext()); + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertTrue(usersCursor.isConsumed()); + } + } - Iterator iterator = usersCursor.iterator(); + @Test + public void testCursorClosedOnSessionClose() { + Cursor usersCursor; + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + usersCursor = mapper.getAllUsers(); - // Check if hasNext, fetching is started - Assert.assertTrue(iterator.hasNext()); - Assert.assertTrue(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); + Assert.assertFalse(usersCursor.isOpen()); - // Consume only the first result - User user = iterator.next(); - Assert.assertEquals("User1", user.getName()); + Iterator iterator = usersCursor.iterator(); - // Check there is still remaining elements - Assert.assertTrue(iterator.hasNext()); - Assert.assertTrue(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); - } + // Check if hasNext, fetching is started + Assert.assertTrue(iterator.hasNext()); + Assert.assertTrue(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); - // The cursor was not fully consumed, but it should be close since we closed the session - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); - } + // Consume only the first result + User user = iterator.next(); + Assert.assertEquals("User1", user.getName()); - @Test - public void testCursorWithRowBound() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - // RowBound starting at offset 1 and limiting to 2 items - Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 3)); - - Iterator iterator = usersCursor.iterator(); - - User user = iterator.next(); - Assert.assertEquals("User2", user.getName()); - Assert.assertEquals(1, usersCursor.getCurrentIndex()); - - // Calling hasNext() before next() - Assert.assertTrue(iterator.hasNext()); - user = iterator.next(); - Assert.assertEquals("User3", user.getName()); - Assert.assertEquals(2, usersCursor.getCurrentIndex()); - - // Calling next() without a previous hasNext() call - user = iterator.next(); - Assert.assertEquals("User4", user.getName()); - Assert.assertEquals(3, usersCursor.getCurrentIndex()); - - Assert.assertFalse(iterator.hasNext()); - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertTrue(usersCursor.isConsumed()); - } + // Check there is still remaining elements + Assert.assertTrue(iterator.hasNext()); + Assert.assertTrue(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); } - @Test - public void testCursorIteratorNoSuchElementExceptionWithHasNext() throws IOException { - - try (SqlSession sqlSession = sqlSessionFactory.openSession(); - Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 1))) { - try { - Iterator iterator = usersCursor.iterator(); - - User user = iterator.next(); - Assert.assertEquals("User2", user.getName()); - Assert.assertEquals(1, usersCursor.getCurrentIndex()); - - Assert.assertFalse(iterator.hasNext()); - iterator.next(); - Assert.fail("We should have failed since we call next() when hasNext() returned false"); - } catch (NoSuchElementException e) { - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertTrue(usersCursor.isConsumed()); - } - } + // The cursor was not fully consumed, but it should be close since we closed the session + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); + } + + @Test + public void testCursorWithRowBound() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + // RowBound starting at offset 1 and limiting to 2 items + Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 3)); + + Iterator iterator = usersCursor.iterator(); + + User user = iterator.next(); + Assert.assertEquals("User2", user.getName()); + Assert.assertEquals(1, usersCursor.getCurrentIndex()); + + // Calling hasNext() before next() + Assert.assertTrue(iterator.hasNext()); + user = iterator.next(); + Assert.assertEquals("User3", user.getName()); + Assert.assertEquals(2, usersCursor.getCurrentIndex()); + + // Calling next() without a previous hasNext() call + user = iterator.next(); + Assert.assertEquals("User4", user.getName()); + Assert.assertEquals(3, usersCursor.getCurrentIndex()); + + Assert.assertFalse(iterator.hasNext()); + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertTrue(usersCursor.isConsumed()); } + } - @Test - public void testCursorIteratorNoSuchElementExceptionNoHasNext() throws IOException { - try (SqlSession sqlSession = sqlSessionFactory.openSession(); - Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 1))) { - try { - Iterator iterator = usersCursor.iterator(); - User user = iterator.next(); - Assert.assertEquals("User2", user.getName()); - Assert.assertEquals(1, usersCursor.getCurrentIndex()); - - // Trying next() without hasNext() - iterator.next(); - Assert.fail("We should have failed since we call next() when is no more items"); - } catch (NoSuchElementException e) { - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertTrue(usersCursor.isConsumed()); - } - } - } + @Test + public void testCursorIteratorNoSuchElementExceptionWithHasNext() throws IOException { + try (SqlSession sqlSession = sqlSessionFactory.openSession(); + Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 1))) { + try { + Iterator iterator = usersCursor.iterator(); - @Test - public void testCursorWithBadRowBound() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - // Trying to start at offset 10 (which does not exist, since there is only 4 items) - Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(10, 2)); - Iterator iterator = usersCursor.iterator(); + User user = iterator.next(); + Assert.assertEquals("User2", user.getName()); + Assert.assertEquals(1, usersCursor.getCurrentIndex()); - Assert.assertFalse(iterator.hasNext()); - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertTrue(usersCursor.isConsumed()); - } + Assert.assertFalse(iterator.hasNext()); + iterator.next(); + Assert.fail("We should have failed since we call next() when hasNext() returned false"); + } catch (NoSuchElementException e) { + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertTrue(usersCursor.isConsumed()); + } + } + } + + @Test + public void testCursorIteratorNoSuchElementExceptionNoHasNext() throws IOException { + try (SqlSession sqlSession = sqlSessionFactory.openSession(); + Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 1))) { + try { + Iterator iterator = usersCursor.iterator(); + User user = iterator.next(); + Assert.assertEquals("User2", user.getName()); + Assert.assertEquals(1, usersCursor.getCurrentIndex()); + + // Trying next() without hasNext() + iterator.next(); + Assert.fail("We should have failed since we call next() when is no more items"); + } catch (NoSuchElementException e) { + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertTrue(usersCursor.isConsumed()); + } + } + } + + @Test + public void testCursorWithBadRowBound() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + // Trying to start at offset 10 (which does not exist, since there is only 4 items) + Cursor usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(10, 2)); + Iterator iterator = usersCursor.iterator(); + + Assert.assertFalse(iterator.hasNext()); + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertTrue(usersCursor.isConsumed()); } + } - @Test - public void testCursorMultipleHasNextCall() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - Mapper mapper = sqlSession.getMapper(Mapper.class); - Cursor usersCursor = mapper.getAllUsers(); + @Test + public void testCursorMultipleHasNextCall() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + Cursor usersCursor = mapper.getAllUsers(); - Iterator iterator = usersCursor.iterator(); + Iterator iterator = usersCursor.iterator(); - Assert.assertEquals(-1, usersCursor.getCurrentIndex()); + Assert.assertEquals(-1, usersCursor.getCurrentIndex()); - User user = iterator.next(); - Assert.assertEquals("User1", user.getName()); - Assert.assertEquals(0, usersCursor.getCurrentIndex()); + User user = iterator.next(); + Assert.assertEquals("User1", user.getName()); + Assert.assertEquals(0, usersCursor.getCurrentIndex()); - Assert.assertTrue(iterator.hasNext()); - Assert.assertTrue(iterator.hasNext()); - Assert.assertTrue(iterator.hasNext()); - // assert that index has not changed after hasNext() call - Assert.assertEquals(0, usersCursor.getCurrentIndex()); - } + Assert.assertTrue(iterator.hasNext()); + Assert.assertTrue(iterator.hasNext()); + Assert.assertTrue(iterator.hasNext()); + // assert that index has not changed after hasNext() call + Assert.assertEquals(0, usersCursor.getCurrentIndex()); } - - @Test - public void testCursorMultipleIteratorCall() { - Iterator iterator2 = null; - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - Mapper mapper = sqlSession.getMapper(Mapper.class); - Cursor usersCursor = mapper.getAllUsers(); - - Iterator iterator = usersCursor.iterator(); - User user = iterator.next(); - Assert.assertEquals("User1", user.getName()); - Assert.assertEquals(0, usersCursor.getCurrentIndex()); - - iterator2 = usersCursor.iterator(); - iterator2.hasNext(); - Assert.fail("We should have failed since calling iterator several times is not allowed"); - } catch (IllegalStateException e) { - Assert.assertNull("iterator2 should be null", iterator2); - return; - } - Assert.fail("Should have returned earlier"); + } + + @Test + public void testCursorMultipleIteratorCall() { + Iterator iterator2 = null; + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + Cursor usersCursor = mapper.getAllUsers(); + + Iterator iterator = usersCursor.iterator(); + User user = iterator.next(); + Assert.assertEquals("User1", user.getName()); + Assert.assertEquals(0, usersCursor.getCurrentIndex()); + + iterator2 = usersCursor.iterator(); + iterator2.hasNext(); + Assert.fail("We should have failed since calling iterator several times is not allowed"); + } catch (IllegalStateException e) { + Assert.assertNull("iterator2 should be null", iterator2); + return; } + Assert.fail("Should have returned earlier"); + } - @Test - public void testCursorMultipleCloseCall() throws IOException { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - Mapper mapper = sqlSession.getMapper(Mapper.class); - Cursor usersCursor = mapper.getAllUsers(); + @Test + public void testCursorMultipleCloseCall() throws IOException { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + Cursor usersCursor = mapper.getAllUsers(); - Assert.assertFalse(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isOpen()); - Iterator iterator = usersCursor.iterator(); + Iterator iterator = usersCursor.iterator(); - // Check if hasNext, fetching is started - Assert.assertTrue(iterator.hasNext()); - Assert.assertTrue(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); + // Check if hasNext, fetching is started + Assert.assertTrue(iterator.hasNext()); + Assert.assertTrue(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); - // Consume only the first result - User user = iterator.next(); - Assert.assertEquals("User1", user.getName()); + // Consume only the first result + User user = iterator.next(); + Assert.assertEquals("User1", user.getName()); - usersCursor.close(); - // Check multiple close are no-op - usersCursor.close(); + usersCursor.close(); + // Check multiple close are no-op + usersCursor.close(); - // hasNext now return false, since the cursor is closed - Assert.assertFalse(iterator.hasNext()); - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); - } + // hasNext now return false, since the cursor is closed + Assert.assertFalse(iterator.hasNext()); + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); } + } - @Test - public void testCursorUsageAfterClose() throws IOException { + @Test + public void testCursorUsageAfterClose() throws IOException { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - Mapper mapper = sqlSession.getMapper(Mapper.class); + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); - Cursor usersCursor = mapper.getAllUsers(); - try { - Iterator iterator = usersCursor.iterator(); - User user = iterator.next(); - Assert.assertEquals("User1", user.getName()); - Assert.assertEquals(0, usersCursor.getCurrentIndex()); + Cursor usersCursor = mapper.getAllUsers(); + try { + Iterator iterator = usersCursor.iterator(); + User user = iterator.next(); + Assert.assertEquals("User1", user.getName()); + Assert.assertEquals(0, usersCursor.getCurrentIndex()); - user = iterator.next(); - Assert.assertEquals("User2", user.getName()); - Assert.assertEquals(1, usersCursor.getCurrentIndex()); + user = iterator.next(); + Assert.assertEquals("User2", user.getName()); + Assert.assertEquals(1, usersCursor.getCurrentIndex()); - usersCursor.close(); + usersCursor.close(); - // hasNext now return false, since the cursor is closed - Assert.assertFalse(iterator.hasNext()); - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); - - // trying next() will fail - iterator.next(); + // hasNext now return false, since the cursor is closed + Assert.assertFalse(iterator.hasNext()); + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); - Assert.fail("We should have failed with NoSuchElementException since Cursor is closed"); - } catch (NoSuchElementException e) { - // We had an exception and current index has not changed - Assert.assertEquals(1, usersCursor.getCurrentIndex()); - usersCursor.close(); - return; - } - } + // trying next() will fail + iterator.next(); - Assert.fail("Should have returned earlier"); + Assert.fail("We should have failed with NoSuchElementException since Cursor is closed"); + } catch (NoSuchElementException e) { + // We had an exception and current index has not changed + Assert.assertEquals(1, usersCursor.getCurrentIndex()); + usersCursor.close(); + return; + } } - @Test - public void shouldGetAllUserUsingAnnotationBasedMapper() { - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - sqlSession.getConfiguration().getMapperRegistry().addMapper(AnnotationMapper.class); - AnnotationMapper mapper = sqlSession.getMapper(AnnotationMapper.class); - Cursor usersCursor = mapper.getAllUsers(); - - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertFalse(usersCursor.isConsumed()); - Assert.assertEquals(-1, usersCursor.getCurrentIndex()); - - List userList = new ArrayList(); - for (User user : usersCursor){ - userList.add(user); - Assert.assertEquals(userList.size() -1, usersCursor.getCurrentIndex()); - } - - Assert.assertFalse(usersCursor.isOpen()); - Assert.assertTrue(usersCursor.isConsumed()); - Assert.assertEquals(4, usersCursor.getCurrentIndex()); - - Assert.assertEquals(5, userList.size()); - User user = userList.get(0); - Assert.assertEquals("User1", user.getName()); - user = userList.get(1); - Assert.assertEquals("User2", user.getName()); - user = userList.get(2); - Assert.assertEquals("User3", user.getName()); - user = userList.get(3); - Assert.assertEquals("User4", user.getName()); - user = userList.get(4); - Assert.assertEquals("User5", user.getName()); - } + Assert.fail("Should have returned earlier"); + } + + @Test + public void shouldGetAllUserUsingAnnotationBasedMapper() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + sqlSession.getConfiguration().getMapperRegistry().addMapper(AnnotationMapper.class); + AnnotationMapper mapper = sqlSession.getMapper(AnnotationMapper.class); + Cursor usersCursor = mapper.getAllUsers(); + + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertFalse(usersCursor.isConsumed()); + Assert.assertEquals(-1, usersCursor.getCurrentIndex()); + + List userList = new ArrayList(); + for (User user : usersCursor) { + userList.add(user); + Assert.assertEquals(userList.size() - 1, usersCursor.getCurrentIndex()); + } + + Assert.assertFalse(usersCursor.isOpen()); + Assert.assertTrue(usersCursor.isConsumed()); + Assert.assertEquals(4, usersCursor.getCurrentIndex()); + + Assert.assertEquals(5, userList.size()); + User user = userList.get(0); + Assert.assertEquals("User1", user.getName()); + user = userList.get(1); + Assert.assertEquals("User2", user.getName()); + user = userList.get(2); + Assert.assertEquals("User3", user.getName()); + user = userList.get(3); + Assert.assertEquals("User4", user.getName()); + user = userList.get(4); + Assert.assertEquals("User5", user.getName()); } + } - @Test - public void shouldThrowIllegalStateExceptionUsingIteratorOnSessionClosed() { - Cursor usersCursor; - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - usersCursor = sqlSession.getMapper(Mapper.class).getAllUsers(); - } - try { - usersCursor.iterator(); - Assert.fail("Should throws the IllegalStateException when call the iterator method after session is closed."); - } catch (IllegalStateException e) { - Assert.assertEquals("A Cursor is already closed.", e.getMessage()); - } - - // verify for checking order - try (SqlSession sqlSession = sqlSessionFactory.openSession()) { - usersCursor = sqlSession.getMapper(Mapper.class).getAllUsers(); - usersCursor.iterator(); - } - try { - usersCursor.iterator(); - Assert.fail("Should throws the IllegalStateException when call the iterator already."); - } catch (IllegalStateException e) { - Assert.assertEquals("Cannot open more than one iterator on a Cursor", e.getMessage()); - } + @Test + public void shouldThrowIllegalStateExceptionUsingIteratorOnSessionClosed() { + Cursor usersCursor; + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + usersCursor = sqlSession.getMapper(Mapper.class).getAllUsers(); + } + try { + usersCursor.iterator(); + Assert.fail("Should throws the IllegalStateException when call the iterator method after session is closed."); + } catch (IllegalStateException e) { + Assert.assertEquals("A Cursor is already closed.", e.getMessage()); + } + // verify for checking order + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + usersCursor = sqlSession.getMapper(Mapper.class).getAllUsers(); + usersCursor.iterator(); } + try { + usersCursor.iterator(); + Assert.fail("Should throws the IllegalStateException when call the iterator already."); + } catch (IllegalStateException e) { + Assert.assertEquals("Cannot open more than one iterator on a Cursor", e.getMessage()); + } + + } }