|
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