Skip to content

Commit 88257f7

Browse files
quaffjhoeller
authored andcommitted
Discard further rows once maxRows has been reached
See #34666 (comment) Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
1 parent d957f8b commit 88257f7

File tree

3 files changed

+87
-17
lines changed

3 files changed

+87
-17
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
* @author Rod Johnson
103103
* @author Juergen Hoeller
104104
* @author Thomas Risberg
105+
* @author Yanming Zhou
105106
* @since May 3, 2001
106107
* @see JdbcOperations
107108
* @see PreparedStatementCreator
@@ -493,12 +494,12 @@ public String getSql() {
493494

494495
@Override
495496
public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
496-
query(sql, new RowCallbackHandlerResultSetExtractor(rch));
497+
query(sql, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows));
497498
}
498499

499500
@Override
500501
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
501-
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
502+
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
502503
}
503504

504505
@Override
@@ -508,7 +509,7 @@ class StreamStatementCallback implements StatementCallback<Stream<T>>, SqlProvid
508509
public Stream<T> doInStatement(Statement stmt) throws SQLException {
509510
ResultSet rs = stmt.executeQuery(sql);
510511
Connection con = stmt.getConnection();
511-
return new ResultSetSpliterator<>(rs, rowMapper).stream().onClose(() -> {
512+
return new ResultSetSpliterator<>(rs, rowMapper, JdbcTemplate.this.maxRows).stream().onClose(() -> {
512513
JdbcUtils.closeResultSet(rs);
513514
JdbcUtils.closeStatement(stmt);
514515
DataSourceUtils.releaseConnection(con, getDataSource());
@@ -773,12 +774,12 @@ private String appendSql(@Nullable String sql, String statement) {
773774

774775
@Override
775776
public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException {
776-
query(psc, new RowCallbackHandlerResultSetExtractor(rch));
777+
query(psc, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows));
777778
}
778779

779780
@Override
780781
public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
781-
query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
782+
query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows));
782783
}
783784

784785
@Override
@@ -799,28 +800,28 @@ public void query(String sql, RowCallbackHandler rch, @Nullable Object @Nullable
799800

800801
@Override
801802
public <T> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException {
802-
return result(query(psc, new RowMapperResultSetExtractor<>(rowMapper)));
803+
return result(query(psc, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
803804
}
804805

805806
@Override
806807
public <T> List<T> query(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException {
807-
return result(query(sql, pss, new RowMapperResultSetExtractor<>(rowMapper)));
808+
return result(query(sql, pss, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
808809
}
809810

810811
@Override
811812
public <T> List<T> query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException {
812-
return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper)));
813+
return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
813814
}
814815

815816
@Deprecated(since = "5.3")
816817
@Override
817818
public <T> List<T> query(String sql, @Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException {
818-
return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper)));
819+
return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
819820
}
820821

821822
@Override
822823
public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException {
823-
return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper)));
824+
return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
824825
}
825826

826827
/**
@@ -845,7 +846,7 @@ public <T> Stream<T> queryForStream(PreparedStatementCreator psc, @Nullable Prep
845846
}
846847
ResultSet rs = ps.executeQuery();
847848
Connection con = ps.getConnection();
848-
return new ResultSetSpliterator<>(rs, rowMapper).stream().onClose(() -> {
849+
return new ResultSetSpliterator<>(rs, rowMapper, this.maxRows).stream().onClose(() -> {
849850
JdbcUtils.closeResultSet(rs);
850851
if (pss instanceof ParameterDisposer parameterDisposer) {
851852
parameterDisposer.cleanupParameters();
@@ -1364,7 +1365,7 @@ protected Map<String, Object> processResultSet(
13641365
}
13651366
else if (param.getRowCallbackHandler() != null) {
13661367
RowCallbackHandler rch = param.getRowCallbackHandler();
1367-
(new RowCallbackHandlerResultSetExtractor(rch)).extractData(rs);
1368+
(new RowCallbackHandlerResultSetExtractor(rch, -1)).extractData(rs);
13681369
return Collections.singletonMap(param.getName(),
13691370
"ResultSet returned from stored procedure was processed");
13701371
}
@@ -1747,13 +1748,17 @@ private static class RowCallbackHandlerResultSetExtractor implements ResultSetEx
17471748

17481749
private final RowCallbackHandler rch;
17491750

1750-
public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {
1751+
private final int maxRows;
1752+
1753+
public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch, int maxRows) {
17511754
this.rch = rch;
1755+
this.maxRows = maxRows;
17521756
}
17531757

17541758
@Override
17551759
public @Nullable Object extractData(ResultSet rs) throws SQLException {
1756-
while (rs.next()) {
1760+
int processed = 0;
1761+
while (rs.next() && (this.maxRows == -1 || (processed++) < this.maxRows)) {
17571762
this.rch.processRow(rs);
17581763
}
17591764
return null;
@@ -1771,17 +1776,20 @@ private static class ResultSetSpliterator<T> implements Spliterator<T> {
17711776

17721777
private final RowMapper<T> rowMapper;
17731778

1779+
private final int maxRows;
1780+
17741781
private int rowNum = 0;
17751782

1776-
public ResultSetSpliterator(ResultSet rs, RowMapper<T> rowMapper) {
1783+
public ResultSetSpliterator(ResultSet rs, RowMapper<T> rowMapper, int maxRows) {
17771784
this.rs = rs;
17781785
this.rowMapper = rowMapper;
1786+
this.maxRows = maxRows;
17791787
}
17801788

17811789
@Override
17821790
public boolean tryAdvance(Consumer<? super T> action) {
17831791
try {
1784-
if (this.rs.next()) {
1792+
if (this.rs.next() && (this.maxRows == -1 || this.rowNum < this.maxRows)) {
17851793
action.accept(this.rowMapper.mapRow(this.rs, this.rowNum++));
17861794
return true;
17871795
}

spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
* you can have executable query objects (containing row-mapping logic) there.
5353
*
5454
* @author Juergen Hoeller
55+
* @author Yanming Zhou
5556
* @since 1.0.2
5657
* @param <T> the result element type
5758
* @see RowMapper
@@ -64,6 +65,8 @@ public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T
6465

6566
private final int rowsExpected;
6667

68+
private final int maxRows;
69+
6770

6871
/**
6972
* Create a new RowMapperResultSetExtractor.
@@ -80,17 +83,29 @@ public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {
8083
* (just used for optimized collection handling)
8184
*/
8285
public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {
86+
this(rowMapper, rowsExpected, -1);
87+
}
88+
89+
/**
90+
* Create a new RowMapperResultSetExtractor.
91+
* @param rowMapper the RowMapper which creates an object for each row
92+
* @param rowsExpected the number of expected rows
93+
* (just used for optimized collection handling)
94+
* @param maxRows the number of max rows
95+
*/
96+
public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected, int maxRows) {
8397
Assert.notNull(rowMapper, "RowMapper must not be null");
8498
this.rowMapper = rowMapper;
8599
this.rowsExpected = rowsExpected;
100+
this.maxRows = maxRows;
86101
}
87102

88103

89104
@Override
90105
public List<T> extractData(ResultSet rs) throws SQLException {
91106
List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>());
92107
int rowNum = 0;
93-
while (rs.next()) {
108+
while (rs.next() && (this.maxRows == -1 || rowNum < this.maxRows)) {
94109
results.add(this.rowMapper.mapRow(rs, rowNum++));
95110
}
96111
return results;

spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import java.util.Collections;
3333
import java.util.List;
3434
import java.util.Map;
35+
import java.util.function.BiFunction;
3536
import java.util.function.Consumer;
37+
import java.util.stream.Stream;
3638

3739
import javax.sql.DataSource;
3840

@@ -77,6 +79,7 @@
7779
* @author Thomas Risberg
7880
* @author Juergen Hoeller
7981
* @author Phillip Webb
82+
* @author Yanming Zhou
8083
*/
8184
class JdbcTemplateTests {
8285

@@ -1236,6 +1239,50 @@ public int getBatchSize() {
12361239
Collections.singletonMap("someId", 456));
12371240
}
12381241

1242+
@Test
1243+
void testSkipFurtherRowsOnceMaxRowsHasBeenReachedForRowMapper() throws Exception {
1244+
testDiscardFurtherRowsOnceMaxRowsHasBeenReached((template, sql) ->
1245+
template.query(sql, (rs, rowNum) -> rs.getString(1)));
1246+
}
1247+
1248+
@Test
1249+
void testDiscardFurtherRowsOnceMaxRowsHasBeenReachedForRowCallbackHandler() throws Exception {
1250+
testDiscardFurtherRowsOnceMaxRowsHasBeenReached((template, sql) -> {
1251+
List<String> list = new ArrayList<>();
1252+
template.query(sql, (RowCallbackHandler) rs -> list.add(rs.getString(1)));
1253+
return list;
1254+
});
1255+
}
1256+
1257+
@Test
1258+
void testDiscardFurtherRowsOnceMaxRowsHasBeenReachedForStream() throws Exception {
1259+
testDiscardFurtherRowsOnceMaxRowsHasBeenReached((template, sql) -> {
1260+
try (Stream<String> stream = template.queryForStream(sql, (rs, rowNum) -> rs.getString(1))) {
1261+
return stream.toList();
1262+
}
1263+
});
1264+
}
1265+
1266+
private void testDiscardFurtherRowsOnceMaxRowsHasBeenReached(BiFunction<JdbcTemplate,String,List<String>> function) throws Exception {
1267+
String sql = "SELECT FORENAME FROM CUSTMR";
1268+
String[] results = {"rod", "gary", " portia"};
1269+
int maxRows = 2;
1270+
1271+
given(this.resultSet.next()).willReturn(true, true, true, false);
1272+
given(this.resultSet.getString(1)).willReturn(results[0], results[1], results[2]);
1273+
given(this.connection.createStatement()).willReturn(this.preparedStatement);
1274+
1275+
JdbcTemplate template = new JdbcTemplate();
1276+
template.setDataSource(this.dataSource);
1277+
template.setMaxRows(maxRows);
1278+
1279+
assertThat(function.apply(template, sql)).as("same length").hasSize(maxRows);
1280+
1281+
verify(this.resultSet).close();
1282+
verify(this.preparedStatement).close();
1283+
verify(this.connection).close();
1284+
}
1285+
12391286
private void mockDatabaseMetaData(boolean supportsBatchUpdates) throws SQLException {
12401287
DatabaseMetaData databaseMetaData = mock();
12411288
given(databaseMetaData.getDatabaseProductName()).willReturn("MySQL");

0 commit comments

Comments
 (0)