Browse Source

Polishing.

Refactor convertAndAddParameter method to writeValue(…) decoupling responsibilities for a clearer value conversion code path. Also, refactor collection conversion to functional callback-style and extend test assertions.

See #1323
Original pull request: #1838
pull/1840/head
Mark Paluch 1 year ago
parent
commit
f0d3b5e425
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 125
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java
  2. 4
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

125
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java

@ -23,21 +23,18 @@ import java.sql.JDBCType;
import java.sql.SQLType; import java.sql.SQLType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.jdbc.core.convert.JdbcColumnTypes;
import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.mapping.JdbcValue; import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParameters;
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor; import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.Parameters;
@ -47,7 +44,6 @@ import org.springframework.data.repository.query.SpelEvaluator;
import org.springframework.data.repository.query.SpelQueryContext; import org.springframework.data.repository.query.SpelQueryContext;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
import org.springframework.data.util.TypeUtils;
import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@ -55,7 +51,6 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
@ -75,7 +70,7 @@ import org.springframework.util.ObjectUtils;
*/ */
public class StringBasedJdbcQuery extends AbstractJdbcQuery { public class StringBasedJdbcQuery extends AbstractJdbcQuery {
private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters"; private static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or use the javac flag -parameters";
private final JdbcConverter converter; private final JdbcConverter converter;
private final RowMapperFactory rowMapperFactory; private final RowMapperFactory rowMapperFactory;
private final SpelEvaluator spelEvaluator; private final SpelEvaluator spelEvaluator;
@ -188,77 +183,103 @@ public class StringBasedJdbcQuery extends AbstractJdbcQuery {
private MapSqlParameterSource bindParameters(RelationalParameterAccessor accessor) { private MapSqlParameterSource bindParameters(RelationalParameterAccessor accessor) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
Parameters<?, ?> bindableParameters = accessor.getBindableParameters(); Parameters<?, ?> bindableParameters = accessor.getBindableParameters();
MapSqlParameterSource parameters = new MapSqlParameterSource(
new LinkedHashMap<>(bindableParameters.getNumberOfParameters(), 1.0f));
for (Parameter bindableParameter : bindableParameters) { for (Parameter bindableParameter : bindableParameters) {
convertAndAddParameter(parameters, bindableParameter, accessor.getBindableValue(bindableParameter.getIndex()));
Object value = accessor.getBindableValue(bindableParameter.getIndex());
String parameterName = bindableParameter.getName()
.orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
JdbcParameters.JdbcParameter parameter = getQueryMethod().getParameters()
.getParameter(bindableParameter.getIndex());
JdbcValue jdbcValue = writeValue(value, parameter.getTypeInformation(), parameter);
SQLType jdbcType = jdbcValue.getJdbcType();
if (jdbcType == null) {
parameters.addValue(parameterName, jdbcValue.getValue());
} else {
parameters.addValue(parameterName, jdbcValue.getValue(), jdbcType.getVendorTypeNumber());
}
} }
return parameters; return parameters;
} }
private void convertAndAddParameter(MapSqlParameterSource parameters, Parameter p, Object value) { private JdbcValue writeValue(@Nullable Object value, TypeInformation<?> typeInformation,
JdbcParameters.JdbcParameter parameter) {
String parameterName = p.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED)); if (value == null) {
return JdbcValue.of(value, parameter.getSqlType());
}
JdbcParameters.JdbcParameter parameter = getQueryMethod().getParameters().getParameter(p.getIndex()); if (typeInformation.isCollectionLike() && value instanceof Collection<?> collection) {
TypeInformation<?> typeInformation = parameter.getTypeInformation();
JdbcValue jdbcValue; TypeInformation<?> actualType = typeInformation.getActualType();
if (typeInformation.isCollectionLike() //
&& value instanceof Collection<?> collectionValue//
) {
if ( typeInformation.getActualType().getType().isArray() ){
TypeInformation<?> arrayElementType = typeInformation.getActualType().getActualType(); // tuple-binding
if (actualType != null && actualType.getType().isArray()) {
List<Object[]> mapped = new ArrayList<>(); TypeInformation<?> nestedElementType = actualType.getRequiredActualType();
return writeCollection(collection, JDBCType.OTHER,
array -> writeArrayValue(parameter, array, nestedElementType));
}
for (Object array : collectionValue) { // parameter expansion
int length = Array.getLength(array); return writeCollection(collection, parameter.getActualSqlType(),
Object[] mappedArray = new Object[length]; it -> converter.writeJdbcValue(it, typeInformation.getRequiredActualType(), parameter.getActualSqlType()));
}
for (int i = 0; i < length; i++) { SQLType sqlType = parameter.getSqlType();
Object element = Array.get(array, i); return converter.writeJdbcValue(value, typeInformation, sqlType);
JdbcValue elementJdbcValue = converter.writeJdbcValue(element, arrayElementType, parameter.getActualSqlType()); }
mappedArray[i] = elementJdbcValue.getValue(); private JdbcValue writeCollection(Collection<?> value, SQLType defaultType, Function<Object, Object> mapper) {
}
mapped.add(mappedArray);
}
jdbcValue = JdbcValue.of(mapped, JDBCType.OTHER);
} else { if (value.isEmpty()) {
List<Object> mapped = new ArrayList<>(); return JdbcValue.of(value, defaultType);
SQLType jdbcType = null; }
TypeInformation<?> actualType = typeInformation.getRequiredActualType(); JdbcValue jdbcValue;
for (Object o : collectionValue) { List<Object> mapped = new ArrayList<>(value.size());
JdbcValue elementJdbcValue = converter.writeJdbcValue(o, actualType, parameter.getActualSqlType()); SQLType jdbcType = null;
if (jdbcType == null) {
jdbcType = elementJdbcValue.getJdbcType();
}
mapped.add(elementJdbcValue.getValue()); for (Object o : value) {
}
jdbcValue = JdbcValue.of(mapped, jdbcType); Object mappedValue = mapper.apply(o);
if (mappedValue instanceof JdbcValue jv) {
if (jdbcType == null) {
jdbcType = jv.getJdbcType();
}
mappedValue = jv.getValue();
} }
} else {
SQLType sqlType = parameter.getSqlType(); mapped.add(mappedValue);
jdbcValue = converter.writeJdbcValue(value, typeInformation, sqlType);
} }
SQLType jdbcType = jdbcValue.getJdbcType(); jdbcValue = JdbcValue.of(mapped, jdbcType == null ? defaultType : jdbcType);
if (jdbcType == null) {
parameters.addValue(parameterName, jdbcValue.getValue()); return jdbcValue;
} else { }
parameters.addValue(parameterName, jdbcValue.getValue(), jdbcType.getVendorTypeNumber());
private Object[] writeArrayValue(JdbcParameters.JdbcParameter parameter, Object array,
TypeInformation<?> nestedElementType) {
int length = Array.getLength(array);
Object[] mappedArray = new Object[length];
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
JdbcValue elementJdbcValue = converter.writeJdbcValue(element, nestedElementType, parameter.getActualSqlType());
mappedArray[i] = elementJdbcValue.getValue();
} }
return mappedArray;
} }
RowMapper<Object> determineRowMapper(ResultProcessor resultProcessor, boolean hasDynamicProjection) { RowMapper<Object> determineRowMapper(ResultProcessor resultProcessor, boolean hasDynamicProjection) {

4
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

@ -1350,7 +1350,9 @@ public class JdbcRepositoryIntegrationTests {
new Object[]{three.idProp, "two"} // matches nothing new Object[]{three.idProp, "two"} // matches nothing
); );
repository.findByListInTuple(tuples); List<DummyEntity> result = repository.findByListInTuple(tuples);
assertThat(result).containsOnly(two);
} }
private Root createRoot(String namePrefix) { private Root createRoot(String namePrefix) {

Loading…
Cancel
Save