Introduce Stream variant methods for SqlQuery
Closes GH-34474 Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
This commit is contained in:
committed by
Juergen Hoeller
parent
b7a9bee020
commit
8819c749ce
@@ -18,6 +18,8 @@ package org.springframework.jdbc.object;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -25,6 +27,7 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.support.DataAccessUtils;
|
||||
import org.springframework.jdbc.core.PreparedStatementCreator;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterUtils;
|
||||
@@ -52,6 +55,7 @@ import org.springframework.jdbc.core.namedparam.ParsedSql;
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Thomas Risberg
|
||||
* @author Yanming Zhou
|
||||
* @param <T> the result type
|
||||
* @see SqlUpdate
|
||||
*/
|
||||
@@ -94,6 +98,23 @@ public abstract class SqlQuery<T> extends SqlOperation {
|
||||
return getJdbcTemplate().query(newPreparedStatementCreator(params), rowMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Central stream method. All un-named parameter execution goes through this method.
|
||||
* @param params parameters, similar to JDO query parameters.
|
||||
* Primitive parameters must be represented by their Object wrapper type.
|
||||
* The ordering of parameters is significant.
|
||||
* @param context the contextual information passed to the {@code mapRow}
|
||||
* callback method. The JDBC operation itself doesn't rely on this parameter,
|
||||
* but it can be useful for creating the objects of the result list.
|
||||
* @return a result Stream of objects, one per row of the ResultSet. Normally all these
|
||||
* will be of the same class, although it is possible to use different types.
|
||||
*/
|
||||
public Stream<T> stream(Object @Nullable [] params, @Nullable Map<?, ?> context) throws DataAccessException {
|
||||
validateParameters(params);
|
||||
RowMapper<T> rowMapper = newRowMapper(params, context);
|
||||
return getJdbcTemplate().queryForStream(newPreparedStatementCreator(params), rowMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to execute without context.
|
||||
* @param params parameters for the query. Primitive parameters must
|
||||
@@ -104,6 +125,16 @@ public abstract class SqlQuery<T> extends SqlOperation {
|
||||
return execute(params, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to stream without context.
|
||||
* @param params parameters for the query. Primitive parameters must
|
||||
* be represented by their Object wrapper type. The ordering of parameters is
|
||||
* significant.
|
||||
*/
|
||||
public Stream<T> stream(Object... params) throws DataAccessException {
|
||||
return stream(params, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to execute without parameters.
|
||||
* @param context the contextual information for object creation
|
||||
@@ -112,6 +143,14 @@ public abstract class SqlQuery<T> extends SqlOperation {
|
||||
return execute((Object[]) null, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to stream without parameters.
|
||||
* @param context the contextual information for object creation
|
||||
*/
|
||||
public Stream<T> stream(Map<?, ?> context) throws DataAccessException {
|
||||
return stream(null, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to execute without parameters nor context.
|
||||
*/
|
||||
@@ -119,6 +158,13 @@ public abstract class SqlQuery<T> extends SqlOperation {
|
||||
return execute((Object[]) null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to stream without parameters nor context.
|
||||
*/
|
||||
public Stream<T> stream() throws DataAccessException {
|
||||
return stream(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to execute with a single int parameter and context.
|
||||
* @param p1 single int parameter
|
||||
@@ -202,13 +248,23 @@ public abstract class SqlQuery<T> extends SqlOperation {
|
||||
* will be of the same class, although it is possible to use different types.
|
||||
*/
|
||||
public List<T> executeByNamedParam(Map<String, ?> paramMap, @Nullable Map<?, ?> context) throws DataAccessException {
|
||||
validateNamedParameters(paramMap);
|
||||
ParsedSql parsedSql = getParsedSql();
|
||||
MapSqlParameterSource paramSource = new MapSqlParameterSource(paramMap);
|
||||
String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
|
||||
@Nullable Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, getDeclaredParameters());
|
||||
RowMapper<T> rowMapper = newRowMapper(params, context);
|
||||
return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, params), rowMapper);
|
||||
return queryByNamedParam(paramMap, context, getJdbcTemplate()::query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Central stream method. All named parameter execution goes through this method.
|
||||
* @param paramMap parameters associated with the name specified while declaring
|
||||
* the SqlParameters. Primitive parameters must be represented by their Object wrapper
|
||||
* type. The ordering of parameters is not significant since they are supplied in a
|
||||
* SqlParameterMap which is an implementation of the Map interface.
|
||||
* @param context the contextual information passed to the {@code mapRow}
|
||||
* callback method. The JDBC operation itself doesn't rely on this parameter,
|
||||
* but it can be useful for creating the objects of the result list.
|
||||
* @return a Stream of objects, one per row of the ResultSet. Normally all these
|
||||
* will be of the same class, although it is possible to use different types.
|
||||
*/
|
||||
public Stream<T> streamByNamedParam(Map<String, ?> paramMap, @Nullable Map<?, ?> context) throws DataAccessException {
|
||||
return queryByNamedParam(paramMap, context, getJdbcTemplate()::queryForStream);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,6 +277,15 @@ public abstract class SqlQuery<T> extends SqlOperation {
|
||||
return executeByNamedParam(paramMap, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to stream without context.
|
||||
* @param paramMap parameters associated with the name specified while declaring
|
||||
* the SqlParameters. Primitive parameters must be represented by their Object wrapper
|
||||
* type. The ordering of parameters is not significant.
|
||||
*/
|
||||
public Stream<T> streamByNamedParam(Map<String, ? extends @Nullable Object> paramMap) throws DataAccessException {
|
||||
return streamByNamedParam(paramMap, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic object finder method, used by all other {@code findObject} methods.
|
||||
@@ -342,4 +407,14 @@ public abstract class SqlQuery<T> extends SqlOperation {
|
||||
*/
|
||||
protected abstract RowMapper<T> newRowMapper(@Nullable Object @Nullable [] parameters, @Nullable Map<?, ?> context);
|
||||
|
||||
private <R> R queryByNamedParam(Map<String, ?> paramMap, @Nullable Map<?, ?> context, BiFunction<PreparedStatementCreator, RowMapper<T>, R> queryFunction) {
|
||||
validateNamedParameters(paramMap);
|
||||
ParsedSql parsedSql = getParsedSql();
|
||||
MapSqlParameterSource paramSource = new MapSqlParameterSource(paramMap);
|
||||
String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
|
||||
@Nullable Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, getDeclaredParameters());
|
||||
RowMapper<T> rowMapper = newRowMapper(params, context);
|
||||
return queryFunction.apply(newPreparedStatementCreator(sqlToUse, params), rowMapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -52,6 +53,7 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Trevor Cook
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class SqlQueryTests {
|
||||
|
||||
@@ -125,6 +127,72 @@ class SqlQueryTests {
|
||||
verify(preparedStatement).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStreamWithoutParams() throws SQLException {
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt(1)).willReturn(1);
|
||||
|
||||
SqlQuery<Integer> query = new MappingSqlQueryWithParameters<>() {
|
||||
@Override
|
||||
protected Integer mapRow(ResultSet rs, int rownum, Object @Nullable [] params, @Nullable Map<? ,?> context)
|
||||
throws SQLException {
|
||||
assertThat(params).as("params were null").isNull();
|
||||
assertThat(context).as("context was null").isNull();
|
||||
return rs.getInt(1);
|
||||
}
|
||||
};
|
||||
query.setDataSource(dataSource);
|
||||
query.setSql(SELECT_ID);
|
||||
query.compile();
|
||||
try (Stream<Integer> stream = query.stream()) {
|
||||
List<Integer> list = stream.toList();
|
||||
assertThat(list).containsExactly(1);
|
||||
}
|
||||
verify(connection).prepareStatement(SELECT_ID);
|
||||
verify(resultSet).close();
|
||||
verify(preparedStatement).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStreamByNamedParam() throws SQLException {
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getInt("id")).willReturn(1);
|
||||
given(resultSet.getString("forename")).willReturn("rod");
|
||||
given(connection.prepareStatement(SELECT_ID_FORENAME_NAMED_PARAMETERS_PARSED,
|
||||
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY)
|
||||
).willReturn(preparedStatement);
|
||||
|
||||
SqlQuery<Customer> query = new MappingSqlQueryWithParameters<>() {
|
||||
@Override
|
||||
protected Customer mapRow(ResultSet rs, int rownum, Object @Nullable [] params, @Nullable Map<? ,?> context)
|
||||
throws SQLException {
|
||||
assertThat(params).as("params were not null").isNotNull();
|
||||
assertThat(context).as("context was null").isNull();
|
||||
Customer cust = new Customer();
|
||||
cust.setId(rs.getInt(COLUMN_NAMES[0]));
|
||||
cust.setForename(rs.getString(COLUMN_NAMES[1]));
|
||||
return cust;
|
||||
}
|
||||
};
|
||||
query.declareParameter(new SqlParameter("id", Types.NUMERIC));
|
||||
query.declareParameter(new SqlParameter("country", Types.VARCHAR));
|
||||
query.setDataSource(dataSource);
|
||||
query.setSql(SELECT_ID_FORENAME_NAMED_PARAMETERS);
|
||||
query.compile();
|
||||
try (Stream<Customer> stream = query.streamByNamedParam(Map.of("id", 1, "country", "UK"))) {
|
||||
List<Customer> list = stream.toList();
|
||||
assertThat(list).hasSize(1);
|
||||
Customer customer = list.get(0);
|
||||
assertThat(customer.getId()).isEqualTo(1);
|
||||
assertThat(customer.getForename()).isEqualTo("rod");
|
||||
}
|
||||
verify(connection).prepareStatement(SELECT_ID_FORENAME_NAMED_PARAMETERS_PARSED);
|
||||
verify(preparedStatement).setObject(1, 1, Types.NUMERIC);
|
||||
verify(preparedStatement).setString(2, "UK");
|
||||
verify(resultSet).close();
|
||||
verify(preparedStatement).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQueryWithoutEnoughParams() {
|
||||
MappingSqlQuery<Integer> query = new MappingSqlQuery<>() {
|
||||
|
||||
Reference in New Issue
Block a user