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
This commit is contained in:
Dennis Effing
2021-01-07 09:22:29 +01:00
committed by Jens Schauder
parent d0a1e19099
commit c416d3d0ea
3 changed files with 64 additions and 2 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.data.jdbc.repository.query;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -40,6 +41,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
* @author Maciej Walkowiak
* @author Mark Paluch
* @author Dennis Effing
* @since 2.0
*/
public abstract class AbstractJdbcQuery implements RepositoryQuery {
@@ -88,10 +90,14 @@ public abstract class AbstractJdbcQuery implements RepositoryQuery {
return createModifyingQueryExecutor();
}
if (queryMethod.isCollectionQuery() || queryMethod.isStreamQuery()) {
if (queryMethod.isCollectionQuery()) {
return extractor != null ? getQueryExecution(extractor) : collectionQuery(rowMapper);
}
if (queryMethod.isStreamQuery()) {
return extractor != null ? getQueryExecution(extractor) : streamQuery(rowMapper);
}
return extractor != null ? getQueryExecution(extractor) : singleObjectQuery(rowMapper);
}
@@ -140,6 +146,10 @@ public abstract class AbstractJdbcQuery implements RepositoryQuery {
: returnedType.getReturnedType();
}
private <T> JdbcQueryExecution<Stream<T>> streamQuery(RowMapper<T> rowMapper) {
return (query, parameters) -> operations.queryForStream(query, parameters, rowMapper);
}
private <T> JdbcQueryExecution<T> getQueryExecution(ResultSetExtractor<T> resultSetExtractor) {
return (query, parameters) -> operations.query(query, parameters, resultSetExtractor);
}

View File

@@ -51,6 +51,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Jens Schauder
* @author Kazuki Shimizu
* @author Mark Paluch
* @author Dennis Effing
*/
@Transactional
@ActiveProfiles("hsql")
@@ -173,6 +174,21 @@ public class QueryAnnotationHsqlIntegrationTests {
.containsExactlyInAnyOrder("a", "b");
}
@Test // DATAJDBC-356
public void executeCustomQueryWithNamedParameterAndReturnTypeIsStream() {
repository.save(dummyEntity("a"));
repository.save(dummyEntity("b"));
repository.save(dummyEntity("c"));
Stream<DummyEntity> entities = repository.findByNamedRangeWithNamedParameterAndReturnTypeIsStream("a", "c");
assertThat(entities) //
.extracting(e -> e.name) //
.containsExactlyInAnyOrder("b");
}
@Test // DATAJDBC-175
public void executeCustomQueryWithReturnTypeIsNumber() {
@@ -292,6 +308,10 @@ public class QueryAnnotationHsqlIntegrationTests {
@Query("SELECT * FROM DUMMY_ENTITY")
Stream<DummyEntity> findAllWithReturnTypeIsStream();
@Query("SELECT * FROM DUMMY_ENTITY WHERE name < :upper and name > :lower")
Stream<DummyEntity> findByNamedRangeWithNamedParameterAndReturnTypeIsStream(@Param("lower") String lower,
@Param("upper") String upper);
// DATAJDBC-175
@Query("SELECT count(*) FROM DUMMY_ENTITY WHERE name like concat('%', :name, '%')")
int countByNameContaining(@Param("name") String name);

View File

@@ -22,11 +22,12 @@ import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -39,9 +40,11 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.util.ReflectionUtils;
/**
@@ -52,6 +55,7 @@ import org.springframework.util.ReflectionUtils;
* @author Maciej Walkowiak
* @author Evgeni Dimitrov
* @author Mark Paluch
* @author Dennis Effing
*/
public class StringBasedJdbcQueryUnitTests {
@@ -127,6 +131,28 @@ public class StringBasedJdbcQueryUnitTests {
"RowMapper is not expected to be custom");
}
@Test // DATAJDBC-356
public void streamQueryCallsQueryForStreamOnOperations() {
JdbcQueryMethod queryMethod = createMethod("findAllWithStreamReturnType");
StringBasedJdbcQuery query = createQuery(queryMethod);
query.execute(new Object[] {});
verify(operations).queryForStream(eq("some sql statement"), any(SqlParameterSource.class), any(RowMapper.class));
}
@Test // DATAJDBC-356
void streamQueryFallsBackToCollectionQueryWhenCustomResultSetExtractorIsSpecified() {
JdbcQueryMethod queryMethod = createMethod("findAllWithStreamReturnTypeAndResultSetExtractor");
StringBasedJdbcQuery query = createQuery(queryMethod);
query.execute(new Object[] {});
ArgumentCaptor<ResultSetExtractor> captor = ArgumentCaptor.forClass(ResultSetExtractor.class);
verify(operations).query(eq("some sql statement"), any(SqlParameterSource.class), captor.capture());
assertThat(captor.getValue()).isInstanceOf(CustomResultSetExtractor.class);
}
@Test // GH-774
public void sliceQueryNotSupported() {
@@ -173,6 +199,12 @@ public class StringBasedJdbcQueryUnitTests {
resultSetExtractorClass = CustomResultSetExtractor.class)
List<Object> findAllWithCustomRowMapperAndResultSetExtractor();
@Query(value = "some sql statement")
Stream<Object> findAllWithStreamReturnType();
@Query(value = "some sql statement", resultSetExtractorClass = CustomResultSetExtractor.class)
Stream<Object> findAllWithStreamReturnTypeAndResultSetExtractor();
List<Object> noAnnotation();
@Query(value = "some sql statement")