|
23 | 23 | import java.sql.SQLType;
|
24 | 24 | import java.util.ArrayList;
|
25 | 25 | import java.util.Collection;
|
| 26 | +import java.util.LinkedHashMap; |
26 | 27 | import java.util.List;
|
27 |
| -import java.util.Map; |
28 | 28 | import java.util.function.Function;
|
29 | 29 | import java.util.function.Supplier;
|
30 | 30 |
|
31 | 31 | import org.springframework.beans.BeanInstantiationException;
|
32 | 32 | import org.springframework.beans.BeanUtils;
|
33 | 33 | import org.springframework.beans.factory.BeanFactory;
|
34 |
| -import org.springframework.data.jdbc.core.convert.JdbcColumnTypes; |
35 | 34 | import org.springframework.data.jdbc.core.convert.JdbcConverter;
|
36 | 35 | import org.springframework.data.jdbc.core.mapping.JdbcValue;
|
37 |
| -import org.springframework.data.jdbc.support.JdbcUtil; |
38 | 36 | import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
39 | 37 | import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
|
40 |
| -import org.springframework.data.relational.repository.query.RelationalParameters; |
41 | 38 | import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
|
42 | 39 | import org.springframework.data.repository.query.Parameter;
|
43 | 40 | import org.springframework.data.repository.query.Parameters;
|
|
47 | 44 | import org.springframework.data.repository.query.SpelQueryContext;
|
48 | 45 | import org.springframework.data.util.Lazy;
|
49 | 46 | import org.springframework.data.util.TypeInformation;
|
50 |
| -import org.springframework.data.util.TypeUtils; |
51 | 47 | import org.springframework.jdbc.core.ResultSetExtractor;
|
52 | 48 | import org.springframework.jdbc.core.RowMapper;
|
53 | 49 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
54 | 50 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
55 | 51 | import org.springframework.lang.Nullable;
|
56 | 52 | import org.springframework.util.Assert;
|
57 | 53 | import org.springframework.util.ClassUtils;
|
58 |
| -import org.springframework.util.ConcurrentReferenceHashMap; |
59 | 54 | import org.springframework.util.ObjectUtils;
|
60 | 55 |
|
61 | 56 | /**
|
|
75 | 70 | */
|
76 | 71 | public class StringBasedJdbcQuery extends AbstractJdbcQuery {
|
77 | 72 |
|
78 |
| - 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"; |
| 73 | + 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"; |
79 | 74 | private final JdbcConverter converter;
|
80 | 75 | private final RowMapperFactory rowMapperFactory;
|
81 | 76 | private final SpelEvaluator spelEvaluator;
|
@@ -188,77 +183,103 @@ private JdbcQueryExecution<?> createJdbcQueryExecution(RelationalParameterAccess
|
188 | 183 |
|
189 | 184 | private MapSqlParameterSource bindParameters(RelationalParameterAccessor accessor) {
|
190 | 185 |
|
191 |
| - MapSqlParameterSource parameters = new MapSqlParameterSource(); |
192 |
| - |
193 | 186 | Parameters<?, ?> bindableParameters = accessor.getBindableParameters();
|
| 187 | + MapSqlParameterSource parameters = new MapSqlParameterSource( |
| 188 | + new LinkedHashMap<>(bindableParameters.getNumberOfParameters(), 1.0f)); |
194 | 189 |
|
195 | 190 | for (Parameter bindableParameter : bindableParameters) {
|
196 |
| - convertAndAddParameter(parameters, bindableParameter, accessor.getBindableValue(bindableParameter.getIndex())); |
| 191 | + |
| 192 | + Object value = accessor.getBindableValue(bindableParameter.getIndex()); |
| 193 | + String parameterName = bindableParameter.getName() |
| 194 | + .orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED)); |
| 195 | + JdbcParameters.JdbcParameter parameter = getQueryMethod().getParameters() |
| 196 | + .getParameter(bindableParameter.getIndex()); |
| 197 | + |
| 198 | + JdbcValue jdbcValue = writeValue(value, parameter.getTypeInformation(), parameter); |
| 199 | + SQLType jdbcType = jdbcValue.getJdbcType(); |
| 200 | + |
| 201 | + if (jdbcType == null) { |
| 202 | + parameters.addValue(parameterName, jdbcValue.getValue()); |
| 203 | + } else { |
| 204 | + parameters.addValue(parameterName, jdbcValue.getValue(), jdbcType.getVendorTypeNumber()); |
| 205 | + } |
197 | 206 | }
|
198 | 207 |
|
199 | 208 | return parameters;
|
200 | 209 | }
|
201 | 210 |
|
202 |
| - private void convertAndAddParameter(MapSqlParameterSource parameters, Parameter p, Object value) { |
| 211 | + private JdbcValue writeValue(@Nullable Object value, TypeInformation<?> typeInformation, |
| 212 | + JdbcParameters.JdbcParameter parameter) { |
203 | 213 |
|
204 |
| - String parameterName = p.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED)); |
| 214 | + if (value == null) { |
| 215 | + return JdbcValue.of(value, parameter.getSqlType()); |
| 216 | + } |
205 | 217 |
|
206 |
| - JdbcParameters.JdbcParameter parameter = getQueryMethod().getParameters().getParameter(p.getIndex()); |
207 |
| - TypeInformation<?> typeInformation = parameter.getTypeInformation(); |
| 218 | + if (typeInformation.isCollectionLike() && value instanceof Collection<?> collection) { |
208 | 219 |
|
209 |
| - JdbcValue jdbcValue; |
210 |
| - if (typeInformation.isCollectionLike() // |
211 |
| - && value instanceof Collection<?> collectionValue// |
212 |
| - ) { |
213 |
| - if ( typeInformation.getActualType().getType().isArray() ){ |
| 220 | + TypeInformation<?> actualType = typeInformation.getActualType(); |
214 | 221 |
|
215 |
| - TypeInformation<?> arrayElementType = typeInformation.getActualType().getActualType(); |
| 222 | + // tuple-binding |
| 223 | + if (actualType != null && actualType.getType().isArray()) { |
216 | 224 |
|
217 |
| - List<Object[]> mapped = new ArrayList<>(); |
| 225 | + TypeInformation<?> nestedElementType = actualType.getRequiredActualType(); |
| 226 | + return writeCollection(collection, JDBCType.OTHER, |
| 227 | + array -> writeArrayValue(parameter, array, nestedElementType)); |
| 228 | + } |
218 | 229 |
|
219 |
| - for (Object array : collectionValue) { |
220 |
| - int length = Array.getLength(array); |
221 |
| - Object[] mappedArray = new Object[length]; |
| 230 | + // parameter expansion |
| 231 | + return writeCollection(collection, parameter.getActualSqlType(), |
| 232 | + it -> converter.writeJdbcValue(it, typeInformation.getRequiredActualType(), parameter.getActualSqlType())); |
| 233 | + } |
222 | 234 |
|
223 |
| - for (int i = 0; i < length; i++) { |
224 |
| - Object element = Array.get(array, i); |
225 |
| - JdbcValue elementJdbcValue = converter.writeJdbcValue(element, arrayElementType, parameter.getActualSqlType()); |
| 235 | + SQLType sqlType = parameter.getSqlType(); |
| 236 | + return converter.writeJdbcValue(value, typeInformation, sqlType); |
| 237 | + } |
226 | 238 |
|
227 |
| - mappedArray[i] = elementJdbcValue.getValue(); |
228 |
| - } |
229 |
| - mapped.add(mappedArray); |
230 |
| - } |
231 |
| - jdbcValue = JdbcValue.of(mapped, JDBCType.OTHER); |
| 239 | + private JdbcValue writeCollection(Collection<?> value, SQLType defaultType, Function<Object, Object> mapper) { |
232 | 240 |
|
233 |
| - } else { |
234 |
| - List<Object> mapped = new ArrayList<>(); |
235 |
| - SQLType jdbcType = null; |
| 241 | + if (value.isEmpty()) { |
| 242 | + return JdbcValue.of(value, defaultType); |
| 243 | + } |
236 | 244 |
|
237 |
| - TypeInformation<?> actualType = typeInformation.getRequiredActualType(); |
238 |
| - for (Object o : collectionValue) { |
239 |
| - JdbcValue elementJdbcValue = converter.writeJdbcValue(o, actualType, parameter.getActualSqlType()); |
240 |
| - if (jdbcType == null) { |
241 |
| - jdbcType = elementJdbcValue.getJdbcType(); |
242 |
| - } |
| 245 | + JdbcValue jdbcValue; |
| 246 | + List<Object> mapped = new ArrayList<>(value.size()); |
| 247 | + SQLType jdbcType = null; |
243 | 248 |
|
244 |
| - mapped.add(elementJdbcValue.getValue()); |
245 |
| - } |
| 249 | + for (Object o : value) { |
246 | 250 |
|
247 |
| - jdbcValue = JdbcValue.of(mapped, jdbcType); |
| 251 | + Object mappedValue = mapper.apply(o); |
| 252 | + |
| 253 | + if (mappedValue instanceof JdbcValue jv) { |
| 254 | + if (jdbcType == null) { |
| 255 | + jdbcType = jv.getJdbcType(); |
| 256 | + } |
| 257 | + mappedValue = jv.getValue(); |
248 | 258 | }
|
249 |
| - } else { |
250 | 259 |
|
251 |
| - SQLType sqlType = parameter.getSqlType(); |
252 |
| - jdbcValue = converter.writeJdbcValue(value, typeInformation, sqlType); |
| 260 | + mapped.add(mappedValue); |
253 | 261 | }
|
254 | 262 |
|
255 |
| - SQLType jdbcType = jdbcValue.getJdbcType(); |
256 |
| - if (jdbcType == null) { |
| 263 | + jdbcValue = JdbcValue.of(mapped, jdbcType == null ? defaultType : jdbcType); |
257 | 264 |
|
258 |
| - parameters.addValue(parameterName, jdbcValue.getValue()); |
259 |
| - } else { |
260 |
| - parameters.addValue(parameterName, jdbcValue.getValue(), jdbcType.getVendorTypeNumber()); |
| 265 | + return jdbcValue; |
| 266 | + } |
| 267 | + |
| 268 | + private Object[] writeArrayValue(JdbcParameters.JdbcParameter parameter, Object array, |
| 269 | + TypeInformation<?> nestedElementType) { |
| 270 | + |
| 271 | + int length = Array.getLength(array); |
| 272 | + Object[] mappedArray = new Object[length]; |
| 273 | + |
| 274 | + for (int i = 0; i < length; i++) { |
| 275 | + |
| 276 | + Object element = Array.get(array, i); |
| 277 | + JdbcValue elementJdbcValue = converter.writeJdbcValue(element, nestedElementType, parameter.getActualSqlType()); |
| 278 | + |
| 279 | + mappedArray[i] = elementJdbcValue.getValue(); |
261 | 280 | }
|
| 281 | + |
| 282 | + return mappedArray; |
262 | 283 | }
|
263 | 284 |
|
264 | 285 | RowMapper<Object> determineRowMapper(ResultProcessor resultProcessor, boolean hasDynamicProjection) {
|
|
0 commit comments