Skip to content

Commit c416d3d

Browse files
denniseffingschauder
authored andcommitted
Add support for streamed query results.
Use queryForStream for streamed query results. Since ResultSetExtractor cannot be reasonably be used together with streams it falls back to the existing collection behaviour. Closes #578 Original pull request #903
1 parent d0a1e19 commit c416d3d

File tree

3 files changed

+64
-2
lines changed

3 files changed

+64
-2
lines changed

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.sql.ResultSet;
1919
import java.sql.SQLException;
2020
import java.util.List;
21+
import java.util.stream.Stream;
2122

2223
import org.springframework.core.convert.converter.Converter;
2324
import org.springframework.dao.EmptyResultDataAccessException;
@@ -40,6 +41,7 @@
4041
* @author Oliver Gierke
4142
* @author Maciej Walkowiak
4243
* @author Mark Paluch
44+
* @author Dennis Effing
4345
* @since 2.0
4446
*/
4547
public abstract class AbstractJdbcQuery implements RepositoryQuery {
@@ -88,10 +90,14 @@ protected JdbcQueryExecution<?> getQueryExecution(JdbcQueryMethod queryMethod,
8890
return createModifyingQueryExecutor();
8991
}
9092

91-
if (queryMethod.isCollectionQuery() || queryMethod.isStreamQuery()) {
93+
if (queryMethod.isCollectionQuery()) {
9294
return extractor != null ? getQueryExecution(extractor) : collectionQuery(rowMapper);
9395
}
9496

97+
if (queryMethod.isStreamQuery()) {
98+
return extractor != null ? getQueryExecution(extractor) : streamQuery(rowMapper);
99+
}
100+
95101
return extractor != null ? getQueryExecution(extractor) : singleObjectQuery(rowMapper);
96102
}
97103

@@ -140,6 +146,10 @@ protected Class<?> resolveTypeToRead(ResultProcessor resultProcessor) {
140146
: returnedType.getReturnedType();
141147
}
142148

149+
private <T> JdbcQueryExecution<Stream<T>> streamQuery(RowMapper<T> rowMapper) {
150+
return (query, parameters) -> operations.queryForStream(query, parameters, rowMapper);
151+
}
152+
143153
private <T> JdbcQueryExecution<T> getQueryExecution(ResultSetExtractor<T> resultSetExtractor) {
144154
return (query, parameters) -> operations.query(query, parameters, resultSetExtractor);
145155
}

Diff for: spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java

+20
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
* @author Jens Schauder
5252
* @author Kazuki Shimizu
5353
* @author Mark Paluch
54+
* @author Dennis Effing
5455
*/
5556
@Transactional
5657
@ActiveProfiles("hsql")
@@ -173,6 +174,21 @@ public void executeCustomQueryWithReturnTypeIsStream() {
173174
.containsExactlyInAnyOrder("a", "b");
174175
}
175176

177+
@Test // DATAJDBC-356
178+
public void executeCustomQueryWithNamedParameterAndReturnTypeIsStream() {
179+
180+
repository.save(dummyEntity("a"));
181+
repository.save(dummyEntity("b"));
182+
repository.save(dummyEntity("c"));
183+
184+
Stream<DummyEntity> entities = repository.findByNamedRangeWithNamedParameterAndReturnTypeIsStream("a", "c");
185+
186+
assertThat(entities) //
187+
.extracting(e -> e.name) //
188+
.containsExactlyInAnyOrder("b");
189+
190+
}
191+
176192
@Test // DATAJDBC-175
177193
public void executeCustomQueryWithReturnTypeIsNumber() {
178194

@@ -292,6 +308,10 @@ private interface DummyEntityRepository extends CrudRepository<DummyEntity, Long
292308
@Query("SELECT * FROM DUMMY_ENTITY")
293309
Stream<DummyEntity> findAllWithReturnTypeIsStream();
294310

311+
@Query("SELECT * FROM DUMMY_ENTITY WHERE name < :upper and name > :lower")
312+
Stream<DummyEntity> findByNamedRangeWithNamedParameterAndReturnTypeIsStream(@Param("lower") String lower,
313+
@Param("upper") String upper);
314+
295315
// DATAJDBC-175
296316
@Query("SELECT count(*) FROM DUMMY_ENTITY WHERE name like concat('%', :name, '%')")
297317
int countByNameContaining(@Param("name") String name);

Diff for: spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java

+33-1
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222
import java.sql.ResultSet;
2323
import java.util.List;
2424
import java.util.Properties;
25+
import java.util.stream.Stream;
2526

2627
import org.assertj.core.api.Assertions;
2728
import org.junit.jupiter.api.BeforeEach;
2829
import org.junit.jupiter.api.Test;
29-
30+
import org.mockito.ArgumentCaptor;
3031
import org.springframework.dao.DataAccessException;
3132
import org.springframework.data.domain.Page;
3233
import org.springframework.data.domain.Pageable;
@@ -39,9 +40,11 @@
3940
import org.springframework.data.repository.Repository;
4041
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
4142
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
43+
import org.springframework.data.repository.query.DefaultParameters;
4244
import org.springframework.jdbc.core.ResultSetExtractor;
4345
import org.springframework.jdbc.core.RowMapper;
4446
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
47+
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
4548
import org.springframework.util.ReflectionUtils;
4649

4750
/**
@@ -52,6 +55,7 @@
5255
* @author Maciej Walkowiak
5356
* @author Evgeni Dimitrov
5457
* @author Mark Paluch
58+
* @author Dennis Effing
5559
*/
5660
public class StringBasedJdbcQueryUnitTests {
5761

@@ -127,6 +131,28 @@ public void customResultSetExtractorAndRowMapperGetCombined() {
127131
"RowMapper is not expected to be custom");
128132
}
129133

134+
@Test // DATAJDBC-356
135+
public void streamQueryCallsQueryForStreamOnOperations() {
136+
JdbcQueryMethod queryMethod = createMethod("findAllWithStreamReturnType");
137+
StringBasedJdbcQuery query = createQuery(queryMethod);
138+
139+
query.execute(new Object[] {});
140+
141+
verify(operations).queryForStream(eq("some sql statement"), any(SqlParameterSource.class), any(RowMapper.class));
142+
}
143+
144+
@Test // DATAJDBC-356
145+
void streamQueryFallsBackToCollectionQueryWhenCustomResultSetExtractorIsSpecified() {
146+
JdbcQueryMethod queryMethod = createMethod("findAllWithStreamReturnTypeAndResultSetExtractor");
147+
StringBasedJdbcQuery query = createQuery(queryMethod);
148+
149+
query.execute(new Object[] {});
150+
151+
ArgumentCaptor<ResultSetExtractor> captor = ArgumentCaptor.forClass(ResultSetExtractor.class);
152+
verify(operations).query(eq("some sql statement"), any(SqlParameterSource.class), captor.capture());
153+
assertThat(captor.getValue()).isInstanceOf(CustomResultSetExtractor.class);
154+
}
155+
130156
@Test // GH-774
131157
public void sliceQueryNotSupported() {
132158

@@ -173,6 +199,12 @@ interface MyRepository extends Repository<Object, Long> {
173199
resultSetExtractorClass = CustomResultSetExtractor.class)
174200
List<Object> findAllWithCustomRowMapperAndResultSetExtractor();
175201

202+
@Query(value = "some sql statement")
203+
Stream<Object> findAllWithStreamReturnType();
204+
205+
@Query(value = "some sql statement", resultSetExtractorClass = CustomResultSetExtractor.class)
206+
Stream<Object> findAllWithStreamReturnTypeAndResultSetExtractor();
207+
176208
List<Object> noAnnotation();
177209

178210
@Query(value = "some sql statement")

0 commit comments

Comments
 (0)