Skip to content

Commit 842f582

Browse files
committed
Specify fetchSize/maxRows/queryTimeout per statement in JdbcClient
Closes gh-35155
1 parent c9a0105 commit 842f582

File tree

5 files changed

+146
-23
lines changed

5 files changed

+146
-23
lines changed

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,26 @@ public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
196196
afterPropertiesSet();
197197
}
198198

199+
/**
200+
* Copy constructor for a derived JdbcTemplate.
201+
* @param original the original template to copy from
202+
* @since 7.0
203+
*/
204+
public JdbcTemplate(JdbcAccessor original) {
205+
setDataSource(original.getDataSource());
206+
setExceptionTranslator(original.getExceptionTranslator());
207+
setLazyInit(original.isLazyInit());
208+
if (original instanceof JdbcTemplate originalTemplate) {
209+
setIgnoreWarnings(originalTemplate.isIgnoreWarnings());
210+
setFetchSize(originalTemplate.getFetchSize());
211+
setMaxRows(originalTemplate.getMaxRows());
212+
setQueryTimeout(originalTemplate.getQueryTimeout());
213+
setSkipResultsProcessing(originalTemplate.isSkipResultsProcessing());
214+
setSkipUndeclaredResults(originalTemplate.isSkipUndeclaredResults());
215+
setResultsMapCaseInsensitive(originalTemplate.isResultsMapCaseInsensitive());
216+
}
217+
}
218+
199219

200220
/**
201221
* Set whether we want to ignore JDBC statement warnings ({@link SQLWarning}).
@@ -264,7 +284,7 @@ public int getMaxRows() {
264284
}
265285

266286
/**
267-
* Set the query timeout for statements that this JdbcTemplate executes.
287+
* Set the query timeout (seconds) for statements that this JdbcTemplate executes.
268288
* <p>Default is -1, indicating to use the JDBC driver's default
269289
* (i.e. to not pass a specific query timeout setting on the driver).
270290
* <p>Note: Any timeout specified here will be overridden by the remaining
@@ -277,7 +297,7 @@ public void setQueryTimeout(int queryTimeout) {
277297
}
278298

279299
/**
280-
* Return the query timeout for statements that this JdbcTemplate executes.
300+
* Return the query timeout (seconds) for statements that this JdbcTemplate executes.
281301
*/
282302
public int getQueryTimeout() {
283303
return this.queryTimeout;

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
8787
private final JdbcOperations classicJdbcTemplate;
8888

8989
/** Cache of original SQL String to ParsedSql representation. */
90-
private volatile ConcurrentLruCache<String, ParsedSql> parsedSqlCache =
91-
new ConcurrentLruCache<>(DEFAULT_CACHE_LIMIT, NamedParameterUtils::parseSqlStatement);
90+
private volatile ConcurrentLruCache<String, ParsedSql> parsedSqlCache;
9291

9392

9493
/**
@@ -97,8 +96,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
9796
* @param dataSource the JDBC DataSource to access
9897
*/
9998
public NamedParameterJdbcTemplate(DataSource dataSource) {
100-
Assert.notNull(dataSource, "DataSource must not be null");
101-
this.classicJdbcTemplate = new JdbcTemplate(dataSource);
99+
this(new JdbcTemplate(dataSource));
102100
}
103101

104102
/**
@@ -109,6 +107,19 @@ public NamedParameterJdbcTemplate(DataSource dataSource) {
109107
public NamedParameterJdbcTemplate(JdbcOperations classicJdbcTemplate) {
110108
Assert.notNull(classicJdbcTemplate, "JdbcTemplate must not be null");
111109
this.classicJdbcTemplate = classicJdbcTemplate;
110+
this.parsedSqlCache = new ConcurrentLruCache<>(DEFAULT_CACHE_LIMIT, NamedParameterUtils::parseSqlStatement);
111+
}
112+
113+
/**
114+
* Copy constructor for a derived NamedParameterJdbcTemplate.
115+
* @param original the original NamedParameterJdbcTemplate to copy from
116+
* @param classicJdbcTemplate the actual JdbcTemplate delegate to use
117+
* @since 7.0
118+
*/
119+
public NamedParameterJdbcTemplate(NamedParameterJdbcTemplate original, JdbcTemplate classicJdbcTemplate) {
120+
Assert.notNull(classicJdbcTemplate, "JdbcTemplate must not be null");
121+
this.classicJdbcTemplate = classicJdbcTemplate;
122+
this.parsedSqlCache = original.parsedSqlCache;
112123
}
113124

114125

spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
4646
import org.springframework.jdbc.core.namedparam.SimplePropertySqlParameterSource;
4747
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
48+
import org.springframework.jdbc.support.JdbcAccessor;
4849
import org.springframework.jdbc.support.KeyHolder;
4950
import org.springframework.jdbc.support.rowset.SqlRowSet;
5051
import org.springframework.util.Assert;
@@ -62,8 +63,6 @@
6263
*/
6364
final class DefaultJdbcClient implements JdbcClient {
6465

65-
private final JdbcOperations classicOps;
66-
6766
private final NamedParameterJdbcOperations namedParamOps;
6867

6968
private final ConversionService conversionService;
@@ -81,7 +80,6 @@ public DefaultJdbcClient(JdbcOperations jdbcTemplate) {
8180

8281
public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate, @Nullable ConversionService conversionService) {
8382
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
84-
this.classicOps = jdbcTemplate.getJdbcOperations();
8583
this.namedParamOps = jdbcTemplate;
8684
this.conversionService =
8785
(conversionService != null ? conversionService : DefaultConversionService.getSharedInstance());
@@ -90,22 +88,63 @@ public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate, @Nullable Co
9088

9189
@Override
9290
public StatementSpec sql(String sql) {
93-
return new DefaultStatementSpec(sql);
91+
return new DefaultStatementSpec(sql, this.namedParamOps);
9492
}
9593

9694

9795
private class DefaultStatementSpec implements StatementSpec {
9896

9997
private final String sql;
10098

101-
private final List<Object> indexedParams = new ArrayList<>();
99+
private JdbcOperations classicOps;
100+
101+
private NamedParameterJdbcOperations namedParamOps;
102+
103+
private @Nullable JdbcTemplate customTemplate;
104+
105+
private final List<@Nullable Object> indexedParams = new ArrayList<>();
102106

103107
private final MapSqlParameterSource namedParams = new MapSqlParameterSource();
104108

105109
private SqlParameterSource namedParamSource = this.namedParams;
106110

107-
public DefaultStatementSpec(String sql) {
111+
public DefaultStatementSpec(String sql, NamedParameterJdbcOperations namedParamOps) {
108112
this.sql = sql;
113+
this.classicOps = namedParamOps.getJdbcOperations();
114+
this.namedParamOps = namedParamOps;
115+
}
116+
117+
private JdbcTemplate enforceCustomTemplate() {
118+
if (this.customTemplate == null) {
119+
if (!(this.classicOps instanceof JdbcAccessor original)) {
120+
throw new IllegalStateException(
121+
"Needs to be bound to a JdbcAccessor for custom settings support: " + this.classicOps);
122+
}
123+
this.customTemplate = new JdbcTemplate(original);
124+
this.classicOps = this.customTemplate;
125+
this.namedParamOps = (this.namedParamOps instanceof NamedParameterJdbcTemplate originalNamedParam ?
126+
new NamedParameterJdbcTemplate(originalNamedParam, this.customTemplate) :
127+
new NamedParameterJdbcTemplate(this.customTemplate));
128+
}
129+
return this.customTemplate;
130+
}
131+
132+
@Override
133+
public StatementSpec withFetchSize(int fetchSize) {
134+
enforceCustomTemplate().setFetchSize(fetchSize);
135+
return this;
136+
}
137+
138+
@Override
139+
public StatementSpec withMaxRows(int maxRows) {
140+
enforceCustomTemplate().setMaxRows(maxRows);
141+
return this;
142+
}
143+
144+
@Override
145+
public StatementSpec withQueryTimeout(int queryTimeout) {
146+
enforceCustomTemplate().setQueryTimeout(queryTimeout);
147+
return this;
109148
}
110149

111150
@Override
@@ -220,41 +259,41 @@ public <T> MappedQuerySpec<T> query(RowMapper<T> rowMapper) {
220259
@Override
221260
public void query(RowCallbackHandler rch) {
222261
if (useNamedParams()) {
223-
namedParamOps.query(this.sql, this.namedParamSource, rch);
262+
this.namedParamOps.query(this.sql, this.namedParamSource, rch);
224263
}
225264
else {
226-
classicOps.query(statementCreatorForIndexedParams(), rch);
265+
this.classicOps.query(statementCreatorForIndexedParams(), rch);
227266
}
228267
}
229268

230269
@Override
231270
public <T> T query(ResultSetExtractor<T> rse) {
232271
T result = (useNamedParams() ?
233-
namedParamOps.query(this.sql, this.namedParamSource, rse) :
234-
classicOps.query(statementCreatorForIndexedParams(), rse));
272+
this.namedParamOps.query(this.sql, this.namedParamSource, rse) :
273+
this.classicOps.query(statementCreatorForIndexedParams(), rse));
235274
Assert.state(result != null, "No result from ResultSetExtractor");
236275
return result;
237276
}
238277

239278
@Override
240279
public int update() {
241280
return (useNamedParams() ?
242-
namedParamOps.update(this.sql, this.namedParamSource) :
243-
classicOps.update(statementCreatorForIndexedParams()));
281+
this.namedParamOps.update(this.sql, this.namedParamSource) :
282+
this.classicOps.update(statementCreatorForIndexedParams()));
244283
}
245284

246285
@Override
247286
public int update(KeyHolder generatedKeyHolder) {
248287
return (useNamedParams() ?
249-
namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder) :
250-
classicOps.update(statementCreatorForIndexedParamsWithKeys(null), generatedKeyHolder));
288+
this.namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder) :
289+
this.classicOps.update(statementCreatorForIndexedParamsWithKeys(null), generatedKeyHolder));
251290
}
252291

253292
@Override
254293
public int update(KeyHolder generatedKeyHolder, String... keyColumnNames) {
255294
return (useNamedParams() ?
256-
namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder, keyColumnNames) :
257-
classicOps.update(statementCreatorForIndexedParamsWithKeys(keyColumnNames), generatedKeyHolder));
295+
this.namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder, keyColumnNames) :
296+
this.classicOps.update(statementCreatorForIndexedParamsWithKeys(keyColumnNames), generatedKeyHolder));
258297
}
259298

260299
private boolean useNamedParams() {

spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,30 @@ static JdbcClient create(NamedParameterJdbcOperations jdbcTemplate, ConversionSe
132132
*/
133133
interface StatementSpec {
134134

135+
/**
136+
* Apply the given fetch size to any subsequent query statement.
137+
* @param fetchSize the fetch size
138+
* @since 7.0
139+
* @see org.springframework.jdbc.core.JdbcTemplate#setFetchSize
140+
*/
141+
StatementSpec withFetchSize(int fetchSize);
142+
143+
/**
144+
* Apply the given maximum number of rows to any subsequent query statement.
145+
* @param maxRows the maximum number of rows
146+
* @since 7.0
147+
* @see org.springframework.jdbc.core.JdbcTemplate#setMaxRows
148+
*/
149+
StatementSpec withMaxRows(int maxRows);
150+
151+
/**
152+
* Apply the given query timeout to any subsequent query statement.
153+
* @param queryTimeout the query timeout in seconds
154+
* @since 7.0
155+
* @see org.springframework.jdbc.core.JdbcTemplate#setQueryTimeout
156+
*/
157+
StatementSpec withQueryTimeout(int queryTimeout);
158+
135159
/**
136160
* Bind a positional JDBC statement parameter for "?" placeholder resolution
137161
* by implicit order of parameter value registration.

spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,19 @@ void selectWithReusedNamedParameterFromBeanProperties() {
195195
assertResults(users);
196196
}
197197

198+
@Test
199+
void selectWithReusedNamedParameterAndMaxRows() {
200+
List<User> users = jdbcClient.sql(QUERY1)
201+
.withFetchSize(1)
202+
.withMaxRows(1)
203+
.withQueryTimeout(1)
204+
.param("name", "John")
205+
.query(User.class)
206+
.list();
207+
208+
assertSingleResult(users);
209+
}
210+
198211
@Test
199212
void selectWithReusedNamedParameterList() {
200213
List<User> users = jdbcClient.sql(QUERY2)
@@ -215,15 +228,31 @@ void selectWithReusedNamedParameterListFromBeanProperties() {
215228
assertResults(users);
216229
}
217230

231+
@Test
232+
void selectWithReusedNamedParameterListAndMaxRows() {
233+
List<User> users = jdbcClient.sql(QUERY2)
234+
.withFetchSize(1)
235+
.withMaxRows(1)
236+
.withQueryTimeout(1)
237+
.paramSource(new Names(List.of("John", "Bogus")))
238+
.query(User.class)
239+
.list();
240+
241+
assertSingleResult(users);
242+
}
218243

219244
private static void assertResults(List<User> users) {
220245
assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith"));
221246
}
222247

248+
private static void assertSingleResult(List<User> users) {
249+
assertThat(users).containsExactly(new User(2, "John", "John"));
250+
}
251+
252+
223253
record Name(String name) {}
224254

225255
record Names(List<String> names) {}
226-
227256
}
228257

229258

0 commit comments

Comments
 (0)