Browse Source
Delegates to JdbcTemplate/NamedParameterJdbcTemplate underneath the covers. Supports parameter objects/records through SimplePropertySqlParameterSource. Closes gh-30931pull/30957/head
13 changed files with 2026 additions and 41 deletions
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.jdbc.core.namedparam; |
||||
|
||||
import java.beans.PropertyDescriptor; |
||||
import java.lang.reflect.Field; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.jdbc.core.StatementCreatorUtils; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* {@link SqlParameterSource} implementation that obtains parameter values |
||||
* from bean properties of a given JavaBean object, from component accessors |
||||
* of a record class, or from raw field access. |
||||
* |
||||
* <p>This is a more flexible variant of {@link BeanPropertySqlParameterSource}, |
||||
* with the limitation that it is not able to enumerate its |
||||
* {@link #getParameterNames() parameter names}. |
||||
* |
||||
* <p>In terms of its fallback property discovery algorithm, this class is |
||||
* similar to {@link org.springframework.validation.SimpleErrors} which is |
||||
* also just used for property retrieval purposes (rather than binding). |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
* @see NamedParameterJdbcTemplate |
||||
* @see BeanPropertySqlParameterSource |
||||
*/ |
||||
public class SimplePropertySqlParameterSource extends AbstractSqlParameterSource { |
||||
|
||||
private final Object paramObject; |
||||
|
||||
private final Map<String, Object> descriptorMap = new HashMap<>(); |
||||
|
||||
|
||||
/** |
||||
* Create a new SqlParameterSource for the given bean, record or field holder. |
||||
* @param paramObject the bean, record or field holder instance to wrap |
||||
*/ |
||||
public SimplePropertySqlParameterSource(Object paramObject) { |
||||
Assert.notNull(paramObject, "Parameter object must not be null"); |
||||
this.paramObject = paramObject; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean hasValue(String paramName) { |
||||
return (getDescriptor(paramName) != null); |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public Object getValue(String paramName) throws IllegalArgumentException { |
||||
Object desc = getDescriptor(paramName); |
||||
if (desc instanceof PropertyDescriptor pd) { |
||||
ReflectionUtils.makeAccessible(pd.getReadMethod()); |
||||
return ReflectionUtils.invokeMethod(pd.getReadMethod(), this.paramObject); |
||||
} |
||||
else if (desc instanceof Field field) { |
||||
ReflectionUtils.makeAccessible(field); |
||||
return ReflectionUtils.getField(field, this.paramObject); |
||||
} |
||||
throw new IllegalArgumentException("Cannot retrieve value for parameter '" + paramName + |
||||
"' - neither a getter method nor a raw field found"); |
||||
} |
||||
|
||||
/** |
||||
* Derives a default SQL type from the corresponding property type. |
||||
* @see StatementCreatorUtils#javaTypeToSqlParameterType |
||||
*/ |
||||
@Override |
||||
public int getSqlType(String paramName) { |
||||
int sqlType = super.getSqlType(paramName); |
||||
if (sqlType != TYPE_UNKNOWN) { |
||||
return sqlType; |
||||
} |
||||
Object desc = getDescriptor(paramName); |
||||
if (desc instanceof PropertyDescriptor pd) { |
||||
return StatementCreatorUtils.javaTypeToSqlParameterType(pd.getPropertyType()); |
||||
} |
||||
else if (desc instanceof Field field) { |
||||
return StatementCreatorUtils.javaTypeToSqlParameterType(field.getType()); |
||||
} |
||||
return TYPE_UNKNOWN; |
||||
} |
||||
|
||||
@Nullable |
||||
private Object getDescriptor(String paramName) { |
||||
return this.descriptorMap.computeIfAbsent(paramName, name -> { |
||||
Object pd = BeanUtils.getPropertyDescriptor(this.paramObject.getClass(), name); |
||||
if (pd == null) { |
||||
pd = ReflectionUtils.findField(this.paramObject.getClass(), name); |
||||
} |
||||
return pd; |
||||
}); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,313 @@
@@ -0,0 +1,313 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.jdbc.core.simple; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.stream.Stream; |
||||
|
||||
import javax.sql.DataSource; |
||||
|
||||
import org.springframework.jdbc.core.JdbcOperations; |
||||
import org.springframework.jdbc.core.JdbcTemplate; |
||||
import org.springframework.jdbc.core.ResultSetExtractor; |
||||
import org.springframework.jdbc.core.RowCallbackHandler; |
||||
import org.springframework.jdbc.core.RowMapper; |
||||
import org.springframework.jdbc.core.SqlParameterValue; |
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; |
||||
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.KeyHolder; |
||||
import org.springframework.jdbc.support.rowset.SqlRowSet; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* The default implementation of {@link JdbcClient}, |
||||
* as created by the static factory methods. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
* @see JdbcClient#create(DataSource) |
||||
* @see JdbcClient#create(JdbcOperations) |
||||
* @see JdbcClient#create(NamedParameterJdbcOperations) |
||||
*/ |
||||
class DefaultJdbcClient implements JdbcClient { |
||||
|
||||
private final JdbcOperations classicOps; |
||||
|
||||
private final NamedParameterJdbcOperations namedParamOps; |
||||
|
||||
|
||||
public DefaultJdbcClient(DataSource dataSource) { |
||||
this.classicOps = new JdbcTemplate(dataSource); |
||||
this.namedParamOps = new NamedParameterJdbcTemplate(this.classicOps); |
||||
} |
||||
|
||||
public DefaultJdbcClient(JdbcOperations jdbcTemplate) { |
||||
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null"); |
||||
this.classicOps = jdbcTemplate; |
||||
this.namedParamOps = new NamedParameterJdbcTemplate(jdbcTemplate); |
||||
} |
||||
|
||||
public DefaultJdbcClient(NamedParameterJdbcOperations jdbcTemplate) { |
||||
Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null"); |
||||
this.classicOps = jdbcTemplate.getJdbcOperations(); |
||||
this.namedParamOps = jdbcTemplate; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public StatementSpec sql(String sql) { |
||||
return new DefaultStatementSpec(sql); |
||||
} |
||||
|
||||
|
||||
private class DefaultStatementSpec implements StatementSpec { |
||||
|
||||
private final String sql; |
||||
|
||||
private final List<Object> indexedParams = new ArrayList<>(); |
||||
|
||||
private final MapSqlParameterSource namedParams = new MapSqlParameterSource(); |
||||
|
||||
private SqlParameterSource namedParamSource = this.namedParams; |
||||
|
||||
public DefaultStatementSpec(String sql) { |
||||
this.sql = sql; |
||||
} |
||||
|
||||
@Override |
||||
public StatementSpec param(Object value) { |
||||
this.indexedParams.add(value); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StatementSpec param(int jdbcIndex, Object value) { |
||||
if (jdbcIndex < 1) { |
||||
throw new IllegalArgumentException("Invalid JDBC index: needs to start at 1"); |
||||
} |
||||
int index = jdbcIndex - 1; |
||||
int size = this.indexedParams.size(); |
||||
if (index < size) { |
||||
this.indexedParams.set(index, value); |
||||
} |
||||
else { |
||||
for (int i = size; i < index; i++) { |
||||
this.indexedParams.add(null); |
||||
} |
||||
this.indexedParams.add(value); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StatementSpec param(int jdbcIndex, Object value, int sqlType) { |
||||
return param(jdbcIndex, new SqlParameterValue(sqlType, value)); |
||||
} |
||||
|
||||
@Override |
||||
public StatementSpec param(String name, Object value) { |
||||
this.namedParams.addValue(name, value); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StatementSpec param(String name, Object value, int sqlType) { |
||||
this.namedParams.addValue(name, value, sqlType); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StatementSpec params(List<?> values) { |
||||
this.indexedParams.addAll(values); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StatementSpec params(Map<String, ?> paramMap) { |
||||
this.namedParams.addValues(paramMap); |
||||
return this; |
||||
} |
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"}) |
||||
@Override |
||||
public StatementSpec paramSource(Object namedParamObject) { |
||||
this.namedParamSource = (namedParamObject instanceof Map map ? |
||||
new MapSqlParameterSource(map) : |
||||
new SimplePropertySqlParameterSource(namedParamObject)); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StatementSpec paramSource(SqlParameterSource namedParamSource) { |
||||
this.namedParamSource = namedParamSource; |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public ResultQuerySpec query() { |
||||
return (useNamedParams() ? |
||||
new NamedParamResultQuerySpec() : |
||||
new IndexedParamResultQuerySpec()); |
||||
} |
||||
|
||||
@Override |
||||
public <T> MappedQuerySpec<T> query(RowMapper<T> rowMapper) { |
||||
return (useNamedParams() ? |
||||
new NamedParamMappedQuerySpec<>(rowMapper) : |
||||
new IndexedParamMappedQuerySpec<>(rowMapper)); |
||||
} |
||||
|
||||
@Override |
||||
public void query(RowCallbackHandler rch) { |
||||
if (useNamedParams()) { |
||||
namedParamOps.query(this.sql, this.namedParams, rch); |
||||
} |
||||
else { |
||||
classicOps.query(this.sql, rch, this.indexedParams.toArray()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public <T> T query(ResultSetExtractor<T> rse) { |
||||
T result = (useNamedParams() ? |
||||
namedParamOps.query(this.sql, this.namedParams, rse) : |
||||
classicOps.query(this.sql, rse, this.indexedParams.toArray())); |
||||
Assert.state(result != null, "No result from ResultSetExtractor"); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
public int update() { |
||||
return (useNamedParams() ? |
||||
namedParamOps.update(this.sql, this.namedParamSource) : |
||||
classicOps.update(this.sql, this.indexedParams.toArray())); |
||||
} |
||||
|
||||
@Override |
||||
public int update(KeyHolder generatedKeyHolder) { |
||||
return (useNamedParams() ? |
||||
namedParamOps.update(this.sql, this.namedParamSource, generatedKeyHolder) : |
||||
classicOps.update(this.sql, this.indexedParams.toArray(), generatedKeyHolder)); |
||||
} |
||||
|
||||
private boolean useNamedParams() { |
||||
boolean hasNamedParams = (this.namedParams.hasValues() || this.namedParamSource != this.namedParams); |
||||
if (hasNamedParams && !this.indexedParams.isEmpty()) { |
||||
throw new IllegalStateException("Configure either named or indexed parameters, not both"); |
||||
} |
||||
if (this.namedParams.hasValues() && this.namedParamSource != this.namedParams) { |
||||
throw new IllegalStateException( |
||||
"Configure either individual named parameters or a SqlParameterSource, not both"); |
||||
} |
||||
return hasNamedParams; |
||||
} |
||||
|
||||
|
||||
private class IndexedParamResultQuerySpec implements ResultQuerySpec { |
||||
|
||||
@Override |
||||
public SqlRowSet rowSet() { |
||||
return classicOps.queryForRowSet(sql, indexedParams.toArray()); |
||||
} |
||||
|
||||
@Override |
||||
public List<Map<String, Object>> listOfRows() { |
||||
return classicOps.queryForList(sql, indexedParams.toArray()); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Object> singleRow() { |
||||
return classicOps.queryForMap(sql, indexedParams.toArray()); |
||||
} |
||||
|
||||
@Override |
||||
public <T> List<T> singleColumn(Class<T> requiredType) { |
||||
return classicOps.queryForList(sql, requiredType, indexedParams.toArray()); |
||||
} |
||||
} |
||||
|
||||
|
||||
private class NamedParamResultQuerySpec implements ResultQuerySpec { |
||||
|
||||
@Override |
||||
public SqlRowSet rowSet() { |
||||
return namedParamOps.queryForRowSet(sql, namedParamSource); |
||||
} |
||||
|
||||
@Override |
||||
public List<Map<String, Object>> listOfRows() { |
||||
return namedParamOps.queryForList(sql, namedParamSource); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Object> singleRow() { |
||||
return namedParamOps.queryForMap(sql, namedParamSource); |
||||
} |
||||
|
||||
@Override |
||||
public <T> List<T> singleColumn(Class<T> requiredType) { |
||||
return namedParamOps.queryForList(sql, namedParamSource, requiredType); |
||||
} |
||||
} |
||||
|
||||
|
||||
private class IndexedParamMappedQuerySpec<T> implements MappedQuerySpec<T> { |
||||
|
||||
private final RowMapper<T> rowMapper; |
||||
|
||||
public IndexedParamMappedQuerySpec(RowMapper<T> rowMapper) { |
||||
this.rowMapper = rowMapper; |
||||
} |
||||
|
||||
@Override |
||||
public Stream<T> stream() { |
||||
return classicOps.queryForStream(sql, this.rowMapper, indexedParams.toArray()); |
||||
} |
||||
|
||||
@Override |
||||
public List<T> list() { |
||||
return classicOps.query(sql, this.rowMapper, indexedParams.toArray()); |
||||
} |
||||
} |
||||
|
||||
|
||||
private class NamedParamMappedQuerySpec<T> implements MappedQuerySpec<T> { |
||||
|
||||
private final RowMapper<T> rowMapper; |
||||
|
||||
public NamedParamMappedQuerySpec(RowMapper<T> rowMapper) { |
||||
this.rowMapper = rowMapper; |
||||
} |
||||
|
||||
@Override |
||||
public Stream<T> stream() { |
||||
return namedParamOps.queryForStream(sql, namedParamSource, this.rowMapper); |
||||
} |
||||
|
||||
@Override |
||||
public List<T> list() { |
||||
return namedParamOps.query(sql, namedParamSource, this.rowMapper); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,366 @@
@@ -0,0 +1,366 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.jdbc.core.simple; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.Set; |
||||
import java.util.stream.Stream; |
||||
|
||||
import javax.sql.DataSource; |
||||
|
||||
import org.springframework.dao.support.DataAccessUtils; |
||||
import org.springframework.jdbc.core.JdbcOperations; |
||||
import org.springframework.jdbc.core.ResultSetExtractor; |
||||
import org.springframework.jdbc.core.RowCallbackHandler; |
||||
import org.springframework.jdbc.core.RowMapper; |
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; |
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource; |
||||
import org.springframework.jdbc.support.KeyHolder; |
||||
import org.springframework.jdbc.support.rowset.SqlRowSet; |
||||
|
||||
/** |
||||
* A fluent {@link JdbcClient} with common JDBC query and update operations, |
||||
* supporting JDBC-style positional as well as Spring-style named parameters |
||||
* with a convenient unified facade for JDBC PreparedStatement execution. |
||||
* |
||||
* <p>An example for retrieving a query result as a {@code java.util.Optional}: |
||||
* <pre class="code"> |
||||
* Optional<Integer> value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id") |
||||
* .param("id", 3) |
||||
* .query((rs, rowNum) -> rs.getInt(1)) |
||||
* .optional(); |
||||
* </pre> |
||||
* |
||||
* <p>Delegates to {@link org.springframework.jdbc.core.JdbcTemplate} and |
||||
* {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}. |
||||
* For complex JDBC operations, e.g. batch inserts and stored procedure calls, |
||||
* you may use those lower-level template classes directly - or alternatively, |
||||
* {@link SimpleJdbcInsert} and {@link SimpleJdbcCall}. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
* @see ResultSetExtractor |
||||
* @see RowCallbackHandler |
||||
* @see RowMapper |
||||
* @see JdbcOperations |
||||
* @see NamedParameterJdbcOperations |
||||
* @see org.springframework.jdbc.core.JdbcTemplate |
||||
* @see org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate |
||||
*/ |
||||
public interface JdbcClient { |
||||
|
||||
/** |
||||
* The starting point for any JDBC operation: a custom SQL String. |
||||
* @param sql the SQL query or update statement as a String |
||||
* @return a chained statement specification |
||||
*/ |
||||
StatementSpec sql(String sql); |
||||
|
||||
|
||||
// Static factory methods
|
||||
|
||||
/** |
||||
* Create a {@code JdbcClient} for the given {@link DataSource}. |
||||
* @param dataSource the DataSource to obtain connections from |
||||
*/ |
||||
static JdbcClient create(DataSource dataSource) { |
||||
return new DefaultJdbcClient(dataSource); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@code JdbcClient} for the given {@link JdbcOperations} delegate, |
||||
* typically a {@link org.springframework.jdbc.core.JdbcTemplate}. |
||||
* <p>Use this factory method for reusing existing {@code JdbcTemplate} configuration, |
||||
* including its {@code DataSource}. |
||||
* @param jdbcTemplate the delegate to perform operations on |
||||
*/ |
||||
static JdbcClient create(JdbcOperations jdbcTemplate) { |
||||
return new DefaultJdbcClient(jdbcTemplate); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@code JdbcClient} for the given {@link NamedParameterJdbcOperations} delegate, |
||||
* typically a {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}. |
||||
* <p>Use this factory method for reusing existing {@code NamedParameterJdbcTemplate} |
||||
* configuration, including its underlying {@code JdbcTemplate} and the {@code DataSource}. |
||||
* @param jdbcTemplate the delegate to perform operations on |
||||
*/ |
||||
static JdbcClient create(NamedParameterJdbcOperations jdbcTemplate) { |
||||
return new DefaultJdbcClient(jdbcTemplate); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* A statement specification for parameter bindings and query/update execution. |
||||
*/ |
||||
interface StatementSpec { |
||||
|
||||
/** |
||||
* Bind a positional JDBC statement parameter for "?" placeholder resolution |
||||
* by implicit order of parameter value registration. |
||||
* <p>This is primarily intended for statements with a single or very few |
||||
* parameters, registering each parameter value in the order of the |
||||
* parameter's occurrence in the SQL statement. |
||||
* @param value the parameter value to bind |
||||
* @return this statement specification (for chaining) |
||||
* @see java.sql.PreparedStatement#setObject(int, Object) |
||||
*/ |
||||
StatementSpec param(Object value); |
||||
|
||||
/** |
||||
* Bind a positional JDBC statement parameter for "?" placeholder resolution |
||||
* by explicit JDBC statement parameter index. |
||||
* @param jdbcIndex the JDBC-style index (starting with 1) |
||||
* @param value the parameter value to bind |
||||
* @return this statement specification (for chaining) |
||||
* @see java.sql.PreparedStatement#setObject(int, Object) |
||||
*/ |
||||
StatementSpec param(int jdbcIndex, Object value); |
||||
|
||||
/** |
||||
* Bind a positional JDBC statement parameter for "?" placeholder resolution |
||||
* by explicit JDBC statement parameter index. |
||||
* @param jdbcIndex the JDBC-style index (starting with 1) |
||||
* @param value the parameter value to bind |
||||
* @param sqlType the associated SQL type (see {@link java.sql.Types}) |
||||
* @return this statement specification (for chaining) |
||||
* @see java.sql.PreparedStatement#setObject(int, Object, int) |
||||
*/ |
||||
StatementSpec param(int jdbcIndex, Object value, int sqlType); |
||||
|
||||
/** |
||||
* Bind a named statement parameter for ":x" placeholder resolution, |
||||
* with each "x" name matching a ":x" placeholder in the SQL statement. |
||||
* @param name the parameter name |
||||
* @param value the parameter value to bind |
||||
* @return this statement specification (for chaining) |
||||
* @see org.springframework.jdbc.core.namedparam.MapSqlParameterSource#addValue(String, Object) |
||||
*/ |
||||
StatementSpec param(String name, Object value); |
||||
|
||||
/** |
||||
* Bind a named statement parameter for ":x" placeholder resolution, |
||||
* with each "x" name matching a ":x" placeholder in the SQL statement. |
||||
* @param name the parameter name |
||||
* @param value the parameter value to bind |
||||
* @param sqlType the associated SQL type (see {@link java.sql.Types}) |
||||
* @return this statement specification (for chaining) |
||||
* @see org.springframework.jdbc.core.namedparam.MapSqlParameterSource#addValue(String, Object, int) |
||||
*/ |
||||
StatementSpec param(String name, Object value, int sqlType); |
||||
|
||||
/** |
||||
* Bind a list of positional parameters for "?" placeholder resolution. |
||||
* <p>The given list will be added to existing positional parameters, if any. |
||||
* Each element from the complete list will be bound as a JDBC positional |
||||
* parameter with a corresponding JDBC index (i.e. list index + 1). |
||||
* @param values the parameter values to bind |
||||
* @return this statement specification (for chaining) |
||||
* @see #param(Object) |
||||
*/ |
||||
StatementSpec params(List<?> values); |
||||
|
||||
/** |
||||
* Bind named statement parameters for ":x" placeholder resolution. |
||||
* <p>The given map will be merged into existing named parameters, if any. |
||||
* @param paramMap a map of names and parameter values to bind |
||||
* @return this statement specification (for chaining) |
||||
* @see #param(String, Object) |
||||
*/ |
||||
StatementSpec params(Map<String, ?> paramMap); |
||||
|
||||
/** |
||||
* Bind named statement parameters for ":x" placeholder resolution. |
||||
* <p>The given parameter object will define all named parameters |
||||
* based on its JavaBean properties, record components or raw fields. |
||||
* A Map instance can be provided as a complete parameter source as well. |
||||
* @param namedParamObject a custom parameter object (e.g. a JavaBean or |
||||
* record class) with named properties serving as statement parameters |
||||
* @return this statement specification (for chaining) |
||||
* @see org.springframework.jdbc.core.namedparam.MapSqlParameterSource |
||||
* @see org.springframework.jdbc.core.namedparam.SimplePropertySqlParameterSource |
||||
*/ |
||||
StatementSpec paramSource(Object namedParamObject); |
||||
|
||||
/** |
||||
* Bind named statement parameters for ":x" placeholder resolution. |
||||
* <p>The given parameter source will define all named parameters, |
||||
* possibly associating specific SQL types with each value. |
||||
* @param namedParamSource a custom {@link SqlParameterSource} instance |
||||
* @return this statement specification (for chaining) |
||||
* @see org.springframework.jdbc.core.namedparam.AbstractSqlParameterSource#registerSqlType |
||||
*/ |
||||
StatementSpec paramSource(SqlParameterSource namedParamSource); |
||||
|
||||
/** |
||||
* Proceed towards execution of a query, with several result options |
||||
* available in the returned query specification. |
||||
* @return the result query specification |
||||
* @see java.sql.PreparedStatement#executeQuery() |
||||
*/ |
||||
ResultQuerySpec query(); |
||||
|
||||
/** |
||||
* Proceed towards execution of a mapped query, with several options |
||||
* available in the returned query specification. |
||||
* @param rowMapper the callback for mapping each row in the ResultSet |
||||
* @return the mapped query specification |
||||
* @see java.sql.PreparedStatement#executeQuery() |
||||
*/ |
||||
<T> MappedQuerySpec<T> query(RowMapper<T> rowMapper); |
||||
|
||||
/** |
||||
* Execute a query with the provided SQL statement, |
||||
* processing each row with the given callback. |
||||
* @param rch a callback for processing each row in the ResultSet |
||||
* @see java.sql.PreparedStatement#executeQuery() |
||||
*/ |
||||
void query(RowCallbackHandler rch); |
||||
|
||||
/** |
||||
* Execute a query with the provided SQL statement, |
||||
* returning a result object for the entire ResultSet. |
||||
* @param rse a callback for processing the entire ResultSet |
||||
* @return the value returned by the ResultSetExtractor |
||||
* @see java.sql.PreparedStatement#executeQuery() |
||||
*/ |
||||
<T> T query(ResultSetExtractor<T> rse); |
||||
|
||||
/** |
||||
* Execute the provided SQL statement as an update. |
||||
* @return the number of rows affected |
||||
* @see java.sql.PreparedStatement#executeUpdate() |
||||
*/ |
||||
int update(); |
||||
|
||||
/** |
||||
* Execute the provided SQL statement as an update. |
||||
* @param generatedKeyHolder a KeyHolder that will hold the generated keys |
||||
* (typically a {@link org.springframework.jdbc.support.GeneratedKeyHolder}) |
||||
* @return the number of rows affected |
||||
* @see java.sql.PreparedStatement#executeUpdate() |
||||
*/ |
||||
int update(KeyHolder generatedKeyHolder); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* A specification for simple result queries. |
||||
*/ |
||||
interface ResultQuerySpec { |
||||
|
||||
/** |
||||
* Retrieve the result as a row set. |
||||
* @return a detached row set representation |
||||
* of the original database result |
||||
*/ |
||||
SqlRowSet rowSet(); |
||||
|
||||
/** |
||||
* Retrieve the result as a list of rows, |
||||
* retaining the order from the original database result. |
||||
* @return a (potentially empty) list of rows, |
||||
* with each result row represented as a map of |
||||
* case-insensitive column names to column values |
||||
*/ |
||||
List<Map<String, Object>> listOfRows(); |
||||
|
||||
/** |
||||
* Retrieve a single row result. |
||||
* @return the result row represented as a map of |
||||
* case-insensitive column names to column values |
||||
*/ |
||||
Map<String, Object> singleRow(); |
||||
|
||||
/** |
||||
* Retrieve a single column result, |
||||
* retaining the order from the original database result. |
||||
* @return a (potentially empty) list of rows, with each |
||||
* row represented as a column value of the given type |
||||
*/ |
||||
<T> List<T> singleColumn(Class<T> requiredType); |
||||
|
||||
/** |
||||
* Retrieve a single value result. |
||||
* @return the single row represented as its single |
||||
* column value of the given type |
||||
* @see DataAccessUtils#requiredSingleResult(Collection) |
||||
*/ |
||||
default <T> T singleValue(Class<T> requiredType) { |
||||
return DataAccessUtils.requiredSingleResult(singleColumn(requiredType)); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* A specification for RowMapper-mapped queries. |
||||
* |
||||
* @param <T> the RowMapper-declared result type |
||||
*/ |
||||
interface MappedQuerySpec<T> { |
||||
|
||||
/** |
||||
* Retrieve the result as a lazily resolved stream of mapped objects, |
||||
* retaining the order from the original database result. |
||||
* @return the result Stream, containing mapped objects, needing to be |
||||
* closed once fully processed (e.g. through a try-with-resources clause) |
||||
*/ |
||||
Stream<T> stream(); |
||||
|
||||
/** |
||||
* Retrieve the result as a pre-resolved list of mapped objects, |
||||
* retaining the order from the original database result. |
||||
* @return the result as a detached List, containing mapped objects |
||||
*/ |
||||
List<T> list(); |
||||
|
||||
/** |
||||
* Retrieve the result as an order-preserving set of mapped objects. |
||||
* @return the result as a detached Set, containing mapped objects |
||||
* @see #list() |
||||
* @see LinkedHashSet |
||||
*/ |
||||
default Set<T> set() { |
||||
return new LinkedHashSet<>(list()); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a single result, if available, as an {@link Optional} handle. |
||||
* @return an Optional handle with a single result object or none |
||||
* @see #list() |
||||
* @see DataAccessUtils#optionalResult(Collection) |
||||
*/ |
||||
default Optional<T> optional() { |
||||
return DataAccessUtils.optionalResult(list()); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a single result as a required object instance. |
||||
* @return the single result object (never {@code null}) |
||||
* @see #list() |
||||
* @see DataAccessUtils#requiredSingleResult(Collection) |
||||
*/ |
||||
default T single() { |
||||
return DataAccessUtils.requiredSingleResult(list()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,332 @@
@@ -0,0 +1,332 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.jdbc.core.simple; |
||||
|
||||
import java.sql.Connection; |
||||
import java.sql.DatabaseMetaData; |
||||
import java.sql.PreparedStatement; |
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
import java.sql.Types; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
import java.util.stream.Stream; |
||||
|
||||
import javax.sql.DataSource; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.jdbc.Customer; |
||||
import org.springframework.jdbc.core.SqlParameterValue; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.ArgumentMatchers.anyString; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
*/ |
||||
public class JdbcClientIndexedParameterTests { |
||||
|
||||
private static final String SELECT_NAMED_PARAMETERS = |
||||
"select id, forename from custmr where id = ? and country = ?"; |
||||
private static final String SELECT_NO_PARAMETERS = |
||||
"select id, forename from custmr"; |
||||
|
||||
private static final String UPDATE_NAMED_PARAMETERS = |
||||
"update seat_status set booking_id = null where performance_id = ? and price_band_id = ?"; |
||||
|
||||
private static final String[] COLUMN_NAMES = new String[] {"id", "forename"}; |
||||
|
||||
|
||||
private Connection connection = mock(); |
||||
|
||||
private DataSource dataSource = mock(); |
||||
|
||||
private PreparedStatement preparedStatement = mock(); |
||||
|
||||
private ResultSet resultSet = mock(); |
||||
|
||||
private DatabaseMetaData databaseMetaData = mock(); |
||||
|
||||
private JdbcClient client = JdbcClient.create(dataSource); |
||||
|
||||
private List<Object> params = new ArrayList<>(); |
||||
|
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
given(dataSource.getConnection()).willReturn(connection); |
||||
given(connection.prepareStatement(anyString())).willReturn(preparedStatement); |
||||
given(preparedStatement.getConnection()).willReturn(connection); |
||||
given(preparedStatement.executeQuery()).willReturn(resultSet); |
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("MySQL"); |
||||
given(databaseMetaData.supportsBatchUpdates()).willReturn(true); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void testQueryWithResultSetExtractor() throws SQLException { |
||||
given(resultSet.next()).willReturn(true); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.add(new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.add("UK"); |
||||
Customer cust = client.sql(SELECT_NAMED_PARAMETERS).params(params).query( |
||||
rs -> { |
||||
rs.next(); |
||||
Customer cust1 = new Customer(); |
||||
cust1.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust1.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust1; |
||||
}); |
||||
|
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithResultSetExtractorNoParameters() throws SQLException { |
||||
given(resultSet.next()).willReturn(true); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
Customer cust = client.sql(SELECT_NO_PARAMETERS).query( |
||||
rs -> { |
||||
rs.next(); |
||||
Customer cust1 = new Customer(); |
||||
cust1.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust1.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust1; |
||||
}); |
||||
|
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NO_PARAMETERS); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithRowCallbackHandler() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.add(new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.add("UK"); |
||||
final List<Customer> customers = new ArrayList<>(); |
||||
client.sql(SELECT_NAMED_PARAMETERS).params(params).query(rs -> { |
||||
Customer cust = new Customer(); |
||||
cust.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
customers.add(cust); |
||||
}); |
||||
|
||||
assertThat(customers).hasSize(1); |
||||
assertThat(customers.get(0).getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(customers.get(0).getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithRowCallbackHandlerNoParameters() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
final List<Customer> customers = new ArrayList<>(); |
||||
client.sql(SELECT_NO_PARAMETERS).query(rs -> { |
||||
Customer cust = new Customer(); |
||||
cust.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
customers.add(cust); |
||||
}); |
||||
|
||||
assertThat(customers).hasSize(1); |
||||
assertThat(customers.get(0).getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(customers.get(0).getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NO_PARAMETERS); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithRowMapper() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.add(new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.add("UK"); |
||||
List<Customer> customers = client.sql(SELECT_NAMED_PARAMETERS).params(params).query( |
||||
(rs, rownum) -> { |
||||
Customer cust = new Customer(); |
||||
cust.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust; |
||||
}).list(); |
||||
|
||||
assertThat(customers).hasSize(1); |
||||
Customer cust = customers.get(0); |
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithRowMapperNoParameters() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
Set<Customer> customers = client.sql(SELECT_NO_PARAMETERS).query( |
||||
(rs, rownum) -> { |
||||
Customer cust = new Customer(); |
||||
cust.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust; |
||||
}).set(); |
||||
|
||||
assertThat(customers).hasSize(1); |
||||
Customer cust = customers.iterator().next(); |
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NO_PARAMETERS); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForObjectWithRowMapper() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.add(new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.add("UK"); |
||||
|
||||
Customer cust = client.sql(SELECT_NAMED_PARAMETERS).params(params).query( |
||||
(rs, rownum) -> { |
||||
Customer cust1 = new Customer(); |
||||
cust1.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust1.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust1; |
||||
}).single(); |
||||
|
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForStreamWithRowMapper() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.add(new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.add("UK"); |
||||
AtomicInteger count = new AtomicInteger(); |
||||
|
||||
try (Stream<Customer> s = client.sql(SELECT_NAMED_PARAMETERS).params(params).query( |
||||
(rs, rownum) -> { |
||||
Customer cust1 = new Customer(); |
||||
cust1.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust1.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust1; |
||||
}).stream()) { |
||||
s.forEach(cust -> { |
||||
count.incrementAndGet(); |
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
}); |
||||
} |
||||
|
||||
assertThat(count.get()).isEqualTo(1); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testUpdate() throws SQLException { |
||||
given(preparedStatement.executeUpdate()).willReturn(1); |
||||
|
||||
params.add(1); |
||||
params.add(1); |
||||
int rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).params(params).update(); |
||||
|
||||
assertThat(rowsAffected).isEqualTo(1); |
||||
verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS); |
||||
verify(preparedStatement).setObject(1, 1); |
||||
verify(preparedStatement).setObject(2, 1); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testUpdateWithTypedParameters() throws SQLException { |
||||
given(preparedStatement.executeUpdate()).willReturn(1); |
||||
|
||||
params.add(new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.add(new SqlParameterValue(Types.INTEGER, 1)); |
||||
int rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).params(params).update(); |
||||
|
||||
assertThat(rowsAffected).isEqualTo(1); |
||||
verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setObject(2, 1, Types.INTEGER); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,338 @@
@@ -0,0 +1,338 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.jdbc.core.simple; |
||||
|
||||
import java.sql.Connection; |
||||
import java.sql.DatabaseMetaData; |
||||
import java.sql.PreparedStatement; |
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
import java.sql.Types; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
import java.util.stream.Stream; |
||||
|
||||
import javax.sql.DataSource; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.jdbc.Customer; |
||||
import org.springframework.jdbc.core.SqlParameterValue; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.ArgumentMatchers.anyString; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
*/ |
||||
public class JdbcClientNamedParameterTests { |
||||
|
||||
private static final String SELECT_NAMED_PARAMETERS = |
||||
"select id, forename from custmr where id = :id and country = :country"; |
||||
private static final String SELECT_NAMED_PARAMETERS_PARSED = |
||||
"select id, forename from custmr where id = ? and country = ?"; |
||||
private static final String SELECT_NO_PARAMETERS = |
||||
"select id, forename from custmr"; |
||||
|
||||
private static final String UPDATE_NAMED_PARAMETERS = |
||||
"update seat_status set booking_id = null where performance_id = :perfId and price_band_id = :priceId"; |
||||
private static final String UPDATE_NAMED_PARAMETERS_PARSED = |
||||
"update seat_status set booking_id = null where performance_id = ? and price_band_id = ?"; |
||||
|
||||
private static final String[] COLUMN_NAMES = new String[] {"id", "forename"}; |
||||
|
||||
|
||||
private Connection connection = mock(); |
||||
|
||||
private DataSource dataSource = mock(); |
||||
|
||||
private PreparedStatement preparedStatement = mock(); |
||||
|
||||
private ResultSet resultSet = mock(); |
||||
|
||||
private DatabaseMetaData databaseMetaData = mock(); |
||||
|
||||
private JdbcClient client = JdbcClient.create(dataSource); |
||||
|
||||
private Map<String, Object> params = new HashMap<>(); |
||||
|
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
given(dataSource.getConnection()).willReturn(connection); |
||||
given(connection.prepareStatement(anyString())).willReturn(preparedStatement); |
||||
given(preparedStatement.getConnection()).willReturn(connection); |
||||
given(preparedStatement.executeQuery()).willReturn(resultSet); |
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("MySQL"); |
||||
given(databaseMetaData.supportsBatchUpdates()).willReturn(true); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void testQueryWithResultSetExtractor() throws SQLException { |
||||
given(resultSet.next()).willReturn(true); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.put("country", "UK"); |
||||
Customer cust = client.sql(SELECT_NAMED_PARAMETERS).params(params).query( |
||||
rs -> { |
||||
rs.next(); |
||||
Customer cust1 = new Customer(); |
||||
cust1.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust1.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust1; |
||||
}); |
||||
|
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS_PARSED); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithResultSetExtractorNoParameters() throws SQLException { |
||||
given(resultSet.next()).willReturn(true); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
Customer cust = client.sql(SELECT_NO_PARAMETERS).query( |
||||
rs -> { |
||||
rs.next(); |
||||
Customer cust1 = new Customer(); |
||||
cust1.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust1.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust1; |
||||
}); |
||||
|
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NO_PARAMETERS); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithRowCallbackHandler() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.put("country", "UK"); |
||||
final List<Customer> customers = new ArrayList<>(); |
||||
client.sql(SELECT_NAMED_PARAMETERS).params(params).query(rs -> { |
||||
Customer cust = new Customer(); |
||||
cust.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
customers.add(cust); |
||||
}); |
||||
|
||||
assertThat(customers).hasSize(1); |
||||
assertThat(customers.get(0).getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(customers.get(0).getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS_PARSED); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithRowCallbackHandlerNoParameters() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
final List<Customer> customers = new ArrayList<>(); |
||||
client.sql(SELECT_NO_PARAMETERS).query(rs -> { |
||||
Customer cust = new Customer(); |
||||
cust.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
customers.add(cust); |
||||
}); |
||||
|
||||
assertThat(customers).hasSize(1); |
||||
assertThat(customers.get(0).getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(customers.get(0).getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NO_PARAMETERS); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithRowMapper() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.put("country", "UK"); |
||||
List<Customer> customers = client.sql(SELECT_NAMED_PARAMETERS).params(params).query( |
||||
(rs, rownum) -> { |
||||
Customer cust = new Customer(); |
||||
cust.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust; |
||||
}).list(); |
||||
|
||||
assertThat(customers).hasSize(1); |
||||
Customer cust = customers.get(0); |
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS_PARSED); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWithRowMapperNoParameters() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
Set<Customer> customers = client.sql(SELECT_NO_PARAMETERS).query( |
||||
(rs, rownum) -> { |
||||
Customer cust = new Customer(); |
||||
cust.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust; |
||||
}).set(); |
||||
|
||||
assertThat(customers).hasSize(1); |
||||
Customer cust = customers.iterator().next(); |
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NO_PARAMETERS); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForObjectWithRowMapper() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.put("country", "UK"); |
||||
|
||||
Customer cust = client.sql(SELECT_NAMED_PARAMETERS).params(params).query( |
||||
(rs, rownum) -> { |
||||
Customer cust1 = new Customer(); |
||||
cust1.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust1.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust1; |
||||
}).single(); |
||||
|
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS_PARSED); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForStreamWithRowMapper() throws SQLException { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt("id")).willReturn(1); |
||||
given(resultSet.getString("forename")).willReturn("rod"); |
||||
|
||||
params.put("id", new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.put("country", "UK"); |
||||
AtomicInteger count = new AtomicInteger(); |
||||
|
||||
try (Stream<Customer> s = client.sql(SELECT_NAMED_PARAMETERS).params(params).query( |
||||
(rs, rownum) -> { |
||||
Customer cust1 = new Customer(); |
||||
cust1.setId(rs.getInt(COLUMN_NAMES[0])); |
||||
cust1.setForename(rs.getString(COLUMN_NAMES[1])); |
||||
return cust1; |
||||
}).stream()) { |
||||
s.forEach(cust -> { |
||||
count.incrementAndGet(); |
||||
assertThat(cust.getId()).as("Customer id was assigned correctly").isEqualTo(1); |
||||
assertThat(cust.getForename()).as("Customer forename was assigned correctly").isEqualTo("rod"); |
||||
}); |
||||
} |
||||
|
||||
assertThat(count.get()).isEqualTo(1); |
||||
verify(connection).prepareStatement(SELECT_NAMED_PARAMETERS_PARSED); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setString(2, "UK"); |
||||
verify(resultSet).close(); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testUpdate() throws SQLException { |
||||
given(preparedStatement.executeUpdate()).willReturn(1); |
||||
|
||||
params.put("perfId", 1); |
||||
params.put("priceId", 1); |
||||
int rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).params(params).update(); |
||||
|
||||
assertThat(rowsAffected).isEqualTo(1); |
||||
verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED); |
||||
verify(preparedStatement).setObject(1, 1); |
||||
verify(preparedStatement).setObject(2, 1); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
@Test |
||||
public void testUpdateWithTypedParameters() throws SQLException { |
||||
given(preparedStatement.executeUpdate()).willReturn(1); |
||||
|
||||
params.put("perfId", new SqlParameterValue(Types.DECIMAL, 1)); |
||||
params.put("priceId", new SqlParameterValue(Types.INTEGER, 1)); |
||||
int rowsAffected = client.sql(UPDATE_NAMED_PARAMETERS).params(params).update(); |
||||
|
||||
assertThat(rowsAffected).isEqualTo(1); |
||||
verify(connection).prepareStatement(UPDATE_NAMED_PARAMETERS_PARSED); |
||||
verify(preparedStatement).setObject(1, 1, Types.DECIMAL); |
||||
verify(preparedStatement).setObject(2, 1, Types.INTEGER); |
||||
verify(preparedStatement).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,476 @@
@@ -0,0 +1,476 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.jdbc.core.simple; |
||||
|
||||
import java.sql.Connection; |
||||
import java.sql.PreparedStatement; |
||||
import java.sql.ResultSet; |
||||
import java.sql.ResultSetMetaData; |
||||
import java.sql.Types; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import javax.sql.DataSource; |
||||
|
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.ArgumentMatchers.anyString; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* @author Juergen Hoeller |
||||
* @since 6.1 |
||||
*/ |
||||
public class JdbcClientQueryTests { |
||||
|
||||
private Connection connection = mock(); |
||||
|
||||
private DataSource dataSource = mock(); |
||||
|
||||
private PreparedStatement preparedStatement = mock(); |
||||
|
||||
private ResultSet resultSet = mock(); |
||||
|
||||
private ResultSetMetaData resultSetMetaData = mock(); |
||||
|
||||
private JdbcClient client = JdbcClient.create(dataSource); |
||||
|
||||
|
||||
@BeforeEach |
||||
public void setup() throws Exception { |
||||
given(dataSource.getConnection()).willReturn(connection); |
||||
given(resultSetMetaData.getColumnCount()).willReturn(1); |
||||
given(resultSetMetaData.getColumnLabel(1)).willReturn("age"); |
||||
given(connection.prepareStatement(anyString())).willReturn(preparedStatement); |
||||
given(preparedStatement.executeQuery()).willReturn(resultSet); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void verifyClose() throws Exception { |
||||
verify(preparedStatement).close(); |
||||
verify(resultSet).close(); |
||||
verify(connection).close(); |
||||
} |
||||
|
||||
|
||||
// Indexed parameters
|
||||
|
||||
@Test |
||||
public void testQueryForListWithIndexedParam() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, true, false); |
||||
given(resultSet.getObject(1)).willReturn(11, 12); |
||||
|
||||
List<Map<String, Object>> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < ?") |
||||
.param(3).query().listOfRows(); |
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(2); |
||||
assertThat(li.get(0).get("age")).as("First row is Integer").isEqualTo(11); |
||||
assertThat(li.get(1).get("age")).as("Second row is Integer").isEqualTo(12); |
||||
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForListWithIndexedParamAndEmptyResult() throws Exception { |
||||
given(resultSet.next()).willReturn(false); |
||||
|
||||
List<Map<String, Object>> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < ?") |
||||
.param(3).query().listOfRows(); |
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(0); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForListWithIndexedParamAndSingleRowAndColumn() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getObject(1)).willReturn(11); |
||||
|
||||
List<Map<String, Object>> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < ?") |
||||
.param(3).query().listOfRows(); |
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(1); |
||||
assertThat(li.get(0).get("age")).as("First row is Integer").isEqualTo(11); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForListWithIndexedParamAndIntegerElementAndSingleRowAndColumn() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(11); |
||||
|
||||
List<Integer> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < ?") |
||||
.param(1, 3) |
||||
.query().singleColumn(Integer.class); |
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(1); |
||||
assertThat(li.get(0)).as("First row is Integer").isEqualTo(11); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForMapWithIndexedParamAndSingleRowAndColumn() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getObject(1)).willReturn(11); |
||||
|
||||
Map<String, Object> map = client.sql("SELECT AGE FROM CUSTMR WHERE ID < ?") |
||||
.param(1, 3) |
||||
.query().singleRow(); |
||||
|
||||
assertThat(map.get("age")).as("Row is Integer").isEqualTo(11); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForObjectWithIndexedParamAndRowMapper() throws Exception { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?") |
||||
.param(1, 3) |
||||
.query((rs, rowNum) -> rs.getInt(1)).single(); |
||||
|
||||
assertThat(value).isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForOptionalWithIndexedParamAndRowMapper() throws Exception { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
Optional<Integer> value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?") |
||||
.param(1, 3) |
||||
.query((rs, rowNum) -> rs.getInt(1)).optional(); |
||||
|
||||
assertThat(value.get()).isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForObjectWithIndexedParamAndInteger() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?") |
||||
.param(1, 3) |
||||
.query().singleValue(Integer.class); |
||||
|
||||
assertThat(value).isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForIntWithIndexedParam() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
int i = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?") |
||||
.param(1, 3) |
||||
.query().singleValue(Integer.class); |
||||
|
||||
assertThat(i).as("Return of an int").isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
|
||||
// Named parameters
|
||||
|
||||
@Test |
||||
public void testQueryForListWithNamedParam() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, true, false); |
||||
given(resultSet.getObject(1)).willReturn(11, 12); |
||||
|
||||
List<Map<String, Object>> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < :id") |
||||
.param("id", 3) |
||||
.query().listOfRows(); |
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(2); |
||||
assertThat(li.get(0).get("age")).as("First row is Integer").isEqualTo(11); |
||||
assertThat(li.get(1).get("age")).as("Second row is Integer").isEqualTo(12); |
||||
|
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForListWithNamedParamAndEmptyResult() throws Exception { |
||||
given(resultSet.next()).willReturn(false); |
||||
|
||||
List<Map<String, Object>> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < :id") |
||||
.param("id", 3) |
||||
.query().listOfRows(); |
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(0); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForListWithNamedParamAndSingleRowAndColumn() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getObject(1)).willReturn(11); |
||||
|
||||
List<Map<String, Object>> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < :id") |
||||
.param("id", 3) |
||||
.query().listOfRows(); |
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(1); |
||||
assertThat(li.get(0).get("age")).as("First row is Integer").isEqualTo(11); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForListWithNamedParamAndIntegerElementAndSingleRowAndColumn() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(11); |
||||
|
||||
List<Integer> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < :id") |
||||
.param("id", 3) |
||||
.query().singleColumn(Integer.class); |
||||
|
||||
assertThat(li.size()).as("All rows returned").isEqualTo(1); |
||||
assertThat(li.get(0)).as("First row is Integer").isEqualTo(11); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForMapWithNamedParamAndSingleRowAndColumn() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getObject(1)).willReturn(11); |
||||
|
||||
Map<String, Object> map = client.sql("SELECT AGE FROM CUSTMR WHERE ID < :id") |
||||
.param("id", 3) |
||||
.query().singleRow(); |
||||
|
||||
assertThat(map.get("age")).as("Row is Integer").isEqualTo(11); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID < ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForObjectWithNamedParamAndRowMapper() throws Exception { |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id") |
||||
.param("id", 3) |
||||
.query((rs, rowNum) -> rs.getInt(1)) |
||||
.single(); |
||||
|
||||
assertThat(value).isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForObjectWithNamedParamAndInteger() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id") |
||||
.param("id", 3) |
||||
.query().singleValue(Integer.class); |
||||
|
||||
assertThat(value).isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForObjectWithNamedParamAndList() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)") |
||||
.param("ids", Arrays.asList(3, 4)) |
||||
.query().singleValue(Integer.class); |
||||
|
||||
assertThat(value).isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForObjectWithNamedParamAndListOfExpressionLists() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
List<Object[]> l1 = new ArrayList<>(); |
||||
l1.add(new Object[] {3, "Rod"}); |
||||
l1.add(new Object[] {4, "Juergen"}); |
||||
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN (:multiExpressionList)") |
||||
.param("multiExpressionList", l1) |
||||
.query().singleValue(Integer.class); |
||||
|
||||
assertThat(value).isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN ((?, ?), (?, ?))"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForIntWithNamedParam() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getInt(1)).willReturn(22); |
||||
|
||||
int i = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id") |
||||
.param("id", 3) |
||||
.query().singleValue(Integer.class); |
||||
|
||||
assertThat(i).as("Return of an int").isEqualTo(22); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForLongWithParamBean() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getLong(1)).willReturn(87L); |
||||
|
||||
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id") |
||||
.paramSource(new ParameterBean(3)) |
||||
.query().singleValue(Long.class); |
||||
|
||||
assertThat(l).as("Return of a long").isEqualTo(87); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3, Types.INTEGER); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForLongWithParamBeanWithCollection() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getLong(1)).willReturn(87L); |
||||
|
||||
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)") |
||||
.paramSource(new ParameterCollectionBean(3, 5)) |
||||
.query().singleValue(Long.class); |
||||
|
||||
assertThat(l).as("Return of a long").isEqualTo(87); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)"); |
||||
verify(preparedStatement).setObject(1, 3); |
||||
verify(preparedStatement).setObject(2, 5); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForLongWithParamRecord() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getLong(1)).willReturn(87L); |
||||
|
||||
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id") |
||||
.paramSource(new ParameterRecord(3)) |
||||
.query().singleValue(Long.class); |
||||
|
||||
assertThat(l).as("Return of a long").isEqualTo(87); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3, Types.INTEGER); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryForLongWithParamFieldHolder() throws Exception { |
||||
given(resultSet.getMetaData()).willReturn(resultSetMetaData); |
||||
given(resultSet.next()).willReturn(true, false); |
||||
given(resultSet.getLong(1)).willReturn(87L); |
||||
|
||||
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id") |
||||
.paramSource(new ParameterFieldHolder(3)) |
||||
.query().singleValue(Long.class); |
||||
|
||||
assertThat(l).as("Return of a long").isEqualTo(87); |
||||
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); |
||||
verify(preparedStatement).setObject(1, 3, Types.INTEGER); |
||||
} |
||||
|
||||
|
||||
static class ParameterBean { |
||||
|
||||
private final int id; |
||||
|
||||
public ParameterBean(int id) { |
||||
this.id = id; |
||||
} |
||||
|
||||
public int getId() { |
||||
return id; |
||||
} |
||||
} |
||||
|
||||
|
||||
static class ParameterCollectionBean { |
||||
|
||||
private final Collection<Integer> ids; |
||||
|
||||
public ParameterCollectionBean(Integer... ids) { |
||||
this.ids = Arrays.asList(ids); |
||||
} |
||||
|
||||
public Collection<Integer> getIds() { |
||||
return ids; |
||||
} |
||||
} |
||||
|
||||
|
||||
record ParameterRecord(int id) { |
||||
} |
||||
|
||||
|
||||
static class ParameterFieldHolder { |
||||
|
||||
public ParameterFieldHolder(int id) { |
||||
this.id = id; |
||||
} |
||||
|
||||
public int id; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue