diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index d5d3f21779..c43ed3a736 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -990,87 +990,89 @@ public abstract class jOOQAbstractTest< } @Test - public void testLazyFetching() throws Exception { + public void testFetchLazy() throws Exception { + for (int fetchSize : Arrays.asList(0, 1)) { - // --------------------------------------------------------------------- - // A regular pass through the cursor - // --------------------------------------------------------------------- - Cursor cursor = create().selectFrom(TBook()).orderBy(TBook_ID()).fetchLazy(); + // --------------------------------------------------------------------- + // A regular pass through the cursor + // --------------------------------------------------------------------- + Cursor cursor = create().selectFrom(TBook()).orderBy(TBook_ID()).fetchLazy(fetchSize); - assertTrue(cursor.hasNext()); - assertTrue(cursor.hasNext()); - assertEquals(Integer.valueOf(1), cursor.fetchOne().getValue(TBook_ID())); - assertEquals(Integer.valueOf(2), cursor.fetchOne().getValue(TBook_ID())); + assertTrue(cursor.hasNext()); + assertTrue(cursor.hasNext()); + assertEquals(Integer.valueOf(1), cursor.fetchOne().getValue(TBook_ID())); + assertEquals(Integer.valueOf(2), cursor.fetchOne().getValue(TBook_ID())); - assertTrue(cursor.hasNext()); - assertTrue(cursor.hasNext()); - assertFalse(cursor.isClosed()); + assertTrue(cursor.hasNext()); + assertTrue(cursor.hasNext()); + assertFalse(cursor.isClosed()); - Iterator it = cursor.iterator(); - assertTrue(it.hasNext()); - assertTrue(cursor.hasNext()); - assertTrue(it.hasNext()); - assertTrue(cursor.hasNext()); - assertTrue(it.hasNext()); - assertTrue(cursor.hasNext()); - assertEquals(Integer.valueOf(3), it.next().getValue(TBook_ID())); - assertEquals(Integer.valueOf(4), it.next().getValue(TBook_ID())); - assertFalse(cursor.isClosed()); + Iterator it = cursor.iterator(); + assertTrue(it.hasNext()); + assertTrue(cursor.hasNext()); + assertTrue(it.hasNext()); + assertTrue(cursor.hasNext()); + assertTrue(it.hasNext()); + assertTrue(cursor.hasNext()); + assertEquals(Integer.valueOf(3), it.next().getValue(TBook_ID())); + assertEquals(Integer.valueOf(4), it.next().getValue(TBook_ID())); + assertFalse(cursor.isClosed()); - assertFalse(it.hasNext()); - assertFalse(cursor.hasNext()); - assertFalse(it.hasNext()); - assertFalse(cursor.hasNext()); - assertFalse(it.hasNext()); - assertFalse(cursor.hasNext()); - assertTrue(cursor.isClosed()); + assertFalse(it.hasNext()); + assertFalse(cursor.hasNext()); + assertFalse(it.hasNext()); + assertFalse(cursor.hasNext()); + assertFalse(it.hasNext()); + assertFalse(cursor.hasNext()); + assertTrue(cursor.isClosed()); - assertEquals(null, it.next()); - assertEquals(null, it.next()); - assertEquals(null, cursor.fetchOne()); - assertEquals(null, cursor.fetchOne()); + assertEquals(null, it.next()); + assertEquals(null, it.next()); + assertEquals(null, cursor.fetchOne()); + assertEquals(null, cursor.fetchOne()); - cursor.close(); - cursor.close(); - assertTrue(cursor.isClosed()); + cursor.close(); + cursor.close(); + assertTrue(cursor.isClosed()); - // --------------------------------------------------------------------- - // Prematurely closing the cursor - // --------------------------------------------------------------------- - cursor = create().selectFrom(TBook()).orderBy(TBook_ID()).fetchLazy(); + // --------------------------------------------------------------------- + // Prematurely closing the cursor + // --------------------------------------------------------------------- + cursor = create().selectFrom(TBook()).orderBy(TBook_ID()).fetchLazy(fetchSize); - assertTrue(cursor.hasNext()); - assertTrue(cursor.hasNext()); - assertEquals(Integer.valueOf(1), cursor.fetchOne().getValue(TBook_ID())); - assertEquals(Integer.valueOf(2), cursor.fetchOne().getValue(TBook_ID())); - assertFalse(cursor.isClosed()); + assertTrue(cursor.hasNext()); + assertTrue(cursor.hasNext()); + assertEquals(Integer.valueOf(1), cursor.fetchOne().getValue(TBook_ID())); + assertEquals(Integer.valueOf(2), cursor.fetchOne().getValue(TBook_ID())); + assertFalse(cursor.isClosed()); - cursor.close(); - assertTrue(cursor.isClosed()); - assertFalse(cursor.hasNext()); - assertNull(cursor.fetchOne()); + cursor.close(); + assertTrue(cursor.isClosed()); + assertFalse(cursor.hasNext()); + assertNull(cursor.fetchOne()); - // --------------------------------------------------------------------- - // Fetching several records at once - // --------------------------------------------------------------------- - cursor = create().selectFrom(TBook()).orderBy(TBook_ID()).fetchLazy(); + // --------------------------------------------------------------------- + // Fetching several records at once + // --------------------------------------------------------------------- + cursor = create().selectFrom(TBook()).orderBy(TBook_ID()).fetchLazy(fetchSize); - assertTrue(cursor.fetch(0).isEmpty()); - assertTrue(cursor.fetch(0).isEmpty()); - List list = cursor.fetch(1); + assertTrue(cursor.fetch(0).isEmpty()); + assertTrue(cursor.fetch(0).isEmpty()); + List list = cursor.fetch(1); - assertEquals(1, list.size()); - assertEquals(Integer.valueOf(1), list.get(0).getValue(TBook_ID())); + assertEquals(1, list.size()); + assertEquals(Integer.valueOf(1), list.get(0).getValue(TBook_ID())); - list = cursor.fetch(2); - assertEquals(2, list.size()); - assertEquals(Integer.valueOf(2), list.get(0).getValue(TBook_ID())); - assertEquals(Integer.valueOf(3), list.get(1).getValue(TBook_ID())); + list = cursor.fetch(2); + assertEquals(2, list.size()); + assertEquals(Integer.valueOf(2), list.get(0).getValue(TBook_ID())); + assertEquals(Integer.valueOf(3), list.get(1).getValue(TBook_ID())); - list = cursor.fetch(2); - assertTrue(cursor.isClosed()); - assertEquals(1, list.size()); - assertEquals(Integer.valueOf(4), list.get(0).getValue(TBook_ID())); + list = cursor.fetch(2); + assertTrue(cursor.isClosed()); + assertEquals(1, list.size()); + assertEquals(Integer.valueOf(4), list.get(0).getValue(TBook_ID())); + } } @Test diff --git a/jOOQ/src/main/java/org/jooq/ResultQuery.java b/jOOQ/src/main/java/org/jooq/ResultQuery.java index e65a1752e4..bf1ef1c7d1 100644 --- a/jOOQ/src/main/java/org/jooq/ResultQuery.java +++ b/jOOQ/src/main/java/org/jooq/ResultQuery.java @@ -38,6 +38,7 @@ package org.jooq; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.Statement; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; @@ -83,20 +84,47 @@ public interface ResultQuery extends Query { Result fetch() throws DataAccessException; /** - * Execute the query and return the generated result + * Execute the query and "lazily" return the generated result *

* The returned {@link Cursor} holds a reference to the executed * {@link PreparedStatement} and the associated {@link ResultSet}. Data can * be fetched (or iterated over) lazily, fetching records from the * {@link ResultSet} one by one. *

+ * Depending on your JDBC driver's default behaviour, this may load the + * whole database result into the driver's memory. In order to indicate to + * the driver that you may not want to fetch all records at once, use + * {@link #fetchLazy(int)} + *

* Client code is responsible for closing the cursor after use. * * @return The resulting cursor. * @throws DataAccessException if something went wrong executing the query + * @see #fetchLazy(int) */ Cursor fetchLazy() throws DataAccessException; + /** + * Execute the query and "lazily" return the generated result + *

+ * The returned {@link Cursor} holds a reference to the executed + * {@link PreparedStatement} and the associated {@link ResultSet}. Data can + * be fetched (or iterated over) lazily, fetching records from the + * {@link ResultSet} one by one. + *

+ * Depending on your JDBC driver's behaviour, this will load only + * fetchSize records from the database into memory at once. For + * more details, see also {@link Statement#setFetchSize(int)} + *

+ * Client code is responsible for closing the cursor after use. + * + * @return The resulting cursor. + * @throws DataAccessException if something went wrong executing the query + * @see #fetchLazy() + * @see Statement#setFetchSize(int) + */ + Cursor fetchLazy(int fetchSize) throws DataAccessException; + /** * Execute a query, possibly returning several result sets. *

diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractDelegatingSelect.java b/jOOQ/src/main/java/org/jooq/impl/AbstractDelegatingSelect.java index e194941ff8..c0e0ecd6dc 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractDelegatingSelect.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractDelegatingSelect.java @@ -101,6 +101,11 @@ abstract class AbstractDelegatingSelect return getDelegate().fetchLazy(); } + @Override + public final Cursor fetchLazy(int fetchSize) { + return getDelegate().fetchLazy(fetchSize); + } + @Override public final List> fetchMany() { return getDelegate().fetchMany(); diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java index 01a5a9214e..f390f9a471 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractResultQuery.java @@ -78,13 +78,14 @@ abstract class AbstractResultQuery extends AbstractQuery imple /** * Generated UID */ - private static final long serialVersionUID = -5588344253566055707L; + private static final long serialVersionUID = -5588344253566055707L; - private transient boolean lazy; - private transient boolean many; - private transient Cursor cursor; - private Result result; - private List> results; + private transient boolean lazy; + private transient int size; + private transient boolean many; + private transient Cursor cursor; + private Result result; + private List> results; AbstractResultQuery(Configuration configuration) { super(configuration); @@ -107,6 +108,17 @@ abstract class AbstractResultQuery extends AbstractQuery imple return (ResultQuery) super.bind(index, value); } + @Override + protected final PreparedStatement prepare(Configuration configuration, String sql) throws SQLException { + PreparedStatement statement = super.prepare(configuration, sql); + + if (size > 0) { + statement.setFetchSize(size); + } + + return statement; + } + @Override protected final int execute(Configuration configuration, PreparedStatement statement) throws SQLException { Connection connection = configuration.getConnection(); @@ -184,9 +196,21 @@ abstract class AbstractResultQuery extends AbstractQuery imple @Override public final Cursor fetchLazy() { + return fetchLazy(0); + } + + @Override + public final Cursor fetchLazy(int fetchSize) throws DataAccessException { lazy = true; - execute(); - lazy = false; + size = fetchSize; + + try { + execute(); + } + finally { + lazy = false; + size = 0; + } return cursor; }