Browse Source

Specify fetchSize/maxRows/queryTimeout per statement in JdbcClient

Closes gh-35155
pull/35163/head
Juergen Hoeller 6 months ago
parent
commit
842f582afc
  1. 24
      spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
  2. 19
      spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java
  3. 71
      spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java
  4. 24
      spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java
  5. 31
      spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientIntegrationTests.java

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

@ -196,6 +196,26 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @@ -196,6 +196,26 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
afterPropertiesSet();
}
/**
* Copy constructor for a derived JdbcTemplate.
* @param original the original template to copy from
* @since 7.0
*/
public JdbcTemplate(JdbcAccessor original) {
setDataSource(original.getDataSource());
setExceptionTranslator(original.getExceptionTranslator());
setLazyInit(original.isLazyInit());
if (original instanceof JdbcTemplate originalTemplate) {
setIgnoreWarnings(originalTemplate.isIgnoreWarnings());
setFetchSize(originalTemplate.getFetchSize());
setMaxRows(originalTemplate.getMaxRows());
setQueryTimeout(originalTemplate.getQueryTimeout());
setSkipResultsProcessing(originalTemplate.isSkipResultsProcessing());
setSkipUndeclaredResults(originalTemplate.isSkipUndeclaredResults());
setResultsMapCaseInsensitive(originalTemplate.isResultsMapCaseInsensitive());
}
}
/**
* Set whether we want to ignore JDBC statement warnings ({@link SQLWarning}).
@ -264,7 +284,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @@ -264,7 +284,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
}
/**
* Set the query timeout for statements that this JdbcTemplate executes.
* Set the query timeout (seconds) for statements that this JdbcTemplate executes.
* <p>Default is -1, indicating to use the JDBC driver's default
* (i.e. to not pass a specific query timeout setting on the driver).
* <p>Note: Any timeout specified here will be overridden by the remaining
@ -277,7 +297,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @@ -277,7 +297,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
}
/**
* Return the query timeout for statements that this JdbcTemplate executes.
* Return the query timeout (seconds) for statements that this JdbcTemplate executes.
*/
public int getQueryTimeout() {
return this.queryTimeout;

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

@ -87,8 +87,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations @@ -87,8 +87,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
private final JdbcOperations classicJdbcTemplate;
/** Cache of original SQL String to ParsedSql representation. */
private volatile ConcurrentLruCache<String, ParsedSql> parsedSqlCache =
new ConcurrentLruCache<>(DEFAULT_CACHE_LIMIT, NamedParameterUtils::parseSqlStatement);
private volatile ConcurrentLruCache<String, ParsedSql> parsedSqlCache;
/**
@ -97,8 +96,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations @@ -97,8 +96,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
* @param dataSource the JDBC DataSource to access
*/
public NamedParameterJdbcTemplate(DataSource dataSource) {
Assert.notNull(dataSource, "DataSource must not be null");
this.classicJdbcTemplate = new JdbcTemplate(dataSource);
this(new JdbcTemplate(dataSource));
}
/**
@ -109,6 +107,19 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations @@ -109,6 +107,19 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
public NamedParameterJdbcTemplate(JdbcOperations classicJdbcTemplate) {
Assert.notNull(classicJdbcTemplate, "JdbcTemplate must not be null");
this.classicJdbcTemplate = classicJdbcTemplate;
this.parsedSqlCache = new ConcurrentLruCache<>(DEFAULT_CACHE_LIMIT, NamedParameterUtils::parseSqlStatement);
}
/**
* Copy constructor for a derived NamedParameterJdbcTemplate.
* @param original the original NamedParameterJdbcTemplate to copy from
* @param classicJdbcTemplate the actual JdbcTemplate delegate to use
* @since 7.0
*/
public NamedParameterJdbcTemplate(NamedParameterJdbcTemplate original, JdbcTemplate classicJdbcTemplate) {
Assert.notNull(classicJdbcTemplate, "JdbcTemplate must not be null");
this.classicJdbcTemplate = classicJdbcTemplate;
this.parsedSqlCache = original.parsedSqlCache;
}

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

@ -45,6 +45,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -45,6 +45,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SimplePropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.JdbcAccessor;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.util.Assert;
@ -62,8 +63,6 @@ import org.springframework.util.Assert; @@ -62,8 +63,6 @@ import org.springframework.util.Assert;
*/
final class DefaultJdbcClient implements JdbcClient {
private final JdbcOperations classicOps;
private final NamedParameterJdbcOperations namedParamOps;
private final ConversionService conversionService;
@ -81,7 +80,6 @@ final class DefaultJdbcClient implements JdbcClient { @@ -81,7 +80,6 @@ final class DefaultJdbcClient implements JdbcClient {
public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate, @Nullable ConversionService conversionService) {
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
this.classicOps = jdbcTemplate.getJdbcOperations();
this.namedParamOps = jdbcTemplate;
this.conversionService =
(conversionService != null ? conversionService : DefaultConversionService.getSharedInstance());
@ -90,7 +88,7 @@ final class DefaultJdbcClient implements JdbcClient { @@ -90,7 +88,7 @@ final class DefaultJdbcClient implements JdbcClient {
@Override
public StatementSpec sql(String sql) {
return new DefaultStatementSpec(sql);
return new DefaultStatementSpec(sql, this.namedParamOps);
}
@ -98,14 +96,55 @@ final class DefaultJdbcClient implements JdbcClient { @@ -98,14 +96,55 @@ final class DefaultJdbcClient implements JdbcClient {
private final String sql;
private final List<Object> indexedParams = new ArrayList<>();
private JdbcOperations classicOps;
private NamedParameterJdbcOperations namedParamOps;
private @Nullable JdbcTemplate customTemplate;
private final List<@Nullable Object> indexedParams = new ArrayList<>();
private final MapSqlParameterSource namedParams = new MapSqlParameterSource();
private SqlParameterSource namedParamSource = this.namedParams;
public DefaultStatementSpec(String sql) {
public DefaultStatementSpec(String sql, NamedParameterJdbcOperations namedParamOps) {
this.sql = sql;
this.classicOps = namedParamOps.getJdbcOperations();
this.namedParamOps = namedParamOps;
}
private JdbcTemplate enforceCustomTemplate() {
if (this.customTemplate == null) {
if (!(this.classicOps instanceof JdbcAccessor original)) {
throw new IllegalStateException(
"Needs to be bound to a JdbcAccessor for custom settings support: " + this.classicOps);
}
this.customTemplate = new JdbcTemplate(original);
this.classicOps = this.customTemplate;
this.namedParamOps = (this.namedParamOps instanceof NamedParameterJdbcTemplate originalNamedParam ?
new NamedParameterJdbcTemplate(originalNamedParam, this.customTemplate) :
new NamedParameterJdbcTemplate(this.customTemplate));
}
return this.customTemplate;
}
@Override
public StatementSpec withFetchSize(int fetchSize) {
enforceCustomTemplate().setFetchSize(fetchSize);
return this;
}
@Override
public StatementSpec withMaxRows(int maxRows) {
enforceCustomTemplate().setMaxRows(maxRows);
return this;
}
@Override
public StatementSpec withQueryTimeout(int queryTimeout) {
enforceCustomTemplate().setQueryTimeout(queryTimeout);
return this;
}
@Override
@ -220,18 +259,18 @@ final class DefaultJdbcClient implements JdbcClient { @@ -220,18 +259,18 @@ final class DefaultJdbcClient implements JdbcClient {
@Override
public void query(RowCallbackHandler rch) {
if (useNamedParams()) {
namedParamOps.query(this.sql, this.namedParamSource, rch);
this.namedParamOps.query(this.sql, this.namedParamSource, rch);
}
else {
classicOps.query(statementCreatorForIndexedParams(), rch);
this.classicOps.query(statementCreatorForIndexedParams(), rch);
}
}
@Override
public <T> T query(ResultSetExtractor<T> rse) {
T result = (useNamedParams() ?
namedParamOps.query(this.sql, this.namedParamSource, rse) :
classicOps.query(statementCreatorForIndexedParams(), rse));
this.namedParamOps.query(this.sql, this.namedParamSource, rse) :
this.classicOps.query(statementCreatorForIndexedParams(), rse));
Assert.state(result != null, "No result from ResultSetExtractor");
return result;
}
@ -239,22 +278,22 @@ final class DefaultJdbcClient implements JdbcClient { @@ -239,22 +278,22 @@ final class DefaultJdbcClient implements JdbcClient {
@Override
public int update() {
return (useNamedParams() ?
namedParamOps.update(this.sql, this.namedParamSource) :
classicOps.update(statementCreatorForIndexedParams()));
this.namedParamOps.update(this.sql, this.namedParamSource) :
this.classicOps.update(statementCreatorForIndexedParams()));
}
@Override
public int update(KeyHolder generatedKeyHolder) {
return (useNamedParams() ?
namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder) :
classicOps.update(statementCreatorForIndexedParamsWithKeys(null), generatedKeyHolder));
this.namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder) :
this.classicOps.update(statementCreatorForIndexedParamsWithKeys(null), generatedKeyHolder));
}
@Override
public int update(KeyHolder generatedKeyHolder, String... keyColumnNames) {
return (useNamedParams() ?
namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder, keyColumnNames) :
classicOps.update(statementCreatorForIndexedParamsWithKeys(keyColumnNames), generatedKeyHolder));
this.namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder, keyColumnNames) :
this.classicOps.update(statementCreatorForIndexedParamsWithKeys(keyColumnNames), generatedKeyHolder));
}
private boolean useNamedParams() {

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

@ -132,6 +132,30 @@ public interface JdbcClient { @@ -132,6 +132,30 @@ public interface JdbcClient {
*/
interface StatementSpec {
/**
* Apply the given fetch size to any subsequent query statement.
* @param fetchSize the fetch size
* @since 7.0
* @see org.springframework.jdbc.core.JdbcTemplate#setFetchSize
*/
StatementSpec withFetchSize(int fetchSize);
/**
* Apply the given maximum number of rows to any subsequent query statement.
* @param maxRows the maximum number of rows
* @since 7.0
* @see org.springframework.jdbc.core.JdbcTemplate#setMaxRows
*/
StatementSpec withMaxRows(int maxRows);
/**
* Apply the given query timeout to any subsequent query statement.
* @param queryTimeout the query timeout in seconds
* @since 7.0
* @see org.springframework.jdbc.core.JdbcTemplate#setQueryTimeout
*/
StatementSpec withQueryTimeout(int queryTimeout);
/**
* Bind a positional JDBC statement parameter for "?" placeholder resolution
* by implicit order of parameter value registration.

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

@ -195,6 +195,19 @@ class JdbcClientIntegrationTests { @@ -195,6 +195,19 @@ class JdbcClientIntegrationTests {
assertResults(users);
}
@Test
void selectWithReusedNamedParameterAndMaxRows() {
List<User> users = jdbcClient.sql(QUERY1)
.withFetchSize(1)
.withMaxRows(1)
.withQueryTimeout(1)
.param("name", "John")
.query(User.class)
.list();
assertSingleResult(users);
}
@Test
void selectWithReusedNamedParameterList() {
List<User> users = jdbcClient.sql(QUERY2)
@ -215,15 +228,31 @@ class JdbcClientIntegrationTests { @@ -215,15 +228,31 @@ class JdbcClientIntegrationTests {
assertResults(users);
}
@Test
void selectWithReusedNamedParameterListAndMaxRows() {
List<User> users = jdbcClient.sql(QUERY2)
.withFetchSize(1)
.withMaxRows(1)
.withQueryTimeout(1)
.paramSource(new Names(List.of("John", "Bogus")))
.query(User.class)
.list();
assertSingleResult(users);
}
private static void assertResults(List<User> users) {
assertThat(users).containsExactly(new User(2, "John", "John"), new User(3, "John", "Smith"));
}
private static void assertSingleResult(List<User> users) {
assertThat(users).containsExactly(new User(2, "John", "John"));
}
record Name(String name) {}
record Names(List<String> names) {}
}

Loading…
Cancel
Save