Skip to content

Commit ceb15fe

Browse files
schaudermp911de
authored andcommitted
DATAJDBC-327 - Add support for conversion to JdbcValue and store byte[] as binary.
Some JDBC drivers depend on correct explicit type information in order to pass on parameters to the database. So far CustomConversion had no way to provide that information. With this change one can use @WritingConverter that converts to JdbcTypeAware in order to provide that information. byte[] now also get stored as BINARY. Original pull request: #123.
1 parent a085844 commit ceb15fe

File tree

34 files changed

+733
-130
lines changed

34 files changed

+733
-130
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java

+103-68
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,35 @@
1616
package org.springframework.data.jdbc.core;
1717

1818
import lombok.NonNull;
19-
import lombok.RequiredArgsConstructor;
2019

21-
import java.sql.Connection;
2220
import java.sql.JDBCType;
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
2323
import java.util.HashMap;
24-
import java.util.LinkedHashMap;
24+
import java.util.HashSet;
25+
import java.util.List;
2526
import java.util.Map;
26-
import java.util.stream.Collectors;
27-
import java.util.stream.StreamSupport;
27+
import java.util.function.Predicate;
2828

2929
import org.springframework.dao.DataRetrievalFailureException;
3030
import org.springframework.dao.EmptyResultDataAccessException;
3131
import org.springframework.dao.InvalidDataAccessApiUsageException;
32+
import org.springframework.data.jdbc.core.convert.JdbcConverter;
33+
import org.springframework.data.jdbc.core.convert.JdbcValue;
3234
import org.springframework.data.jdbc.support.JdbcUtil;
35+
import org.springframework.data.mapping.PersistentProperty;
3336
import org.springframework.data.mapping.PersistentPropertyAccessor;
3437
import org.springframework.data.mapping.PersistentPropertyPath;
3538
import org.springframework.data.mapping.PropertyHandler;
36-
import org.springframework.data.relational.core.conversion.RelationalConverter;
3739
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3840
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3941
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
4042
import org.springframework.data.relational.domain.Identifier;
41-
import org.springframework.data.util.ClassTypeInformation;
4243
import org.springframework.jdbc.core.RowMapper;
4344
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
4445
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
4546
import org.springframework.jdbc.support.GeneratedKeyHolder;
47+
import org.springframework.jdbc.support.JdbcUtils;
4648
import org.springframework.jdbc.support.KeyHolder;
4749
import org.springframework.lang.Nullable;
4850
import org.springframework.util.Assert;
@@ -55,27 +57,32 @@
5557
* @author Thomas Lang
5658
* @author Bastian Wilhelm
5759
*/
58-
@RequiredArgsConstructor
5960
public class DefaultDataAccessStrategy implements DataAccessStrategy {
6061

6162
private final @NonNull SqlGeneratorSource sqlGeneratorSource;
6263
private final @NonNull RelationalMappingContext context;
63-
private final @NonNull RelationalConverter converter;
64+
private final @NonNull JdbcConverter converter;
6465
private final @NonNull NamedParameterJdbcOperations operations;
6566
private final @NonNull DataAccessStrategy accessStrategy;
6667

68+
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
69+
JdbcConverter converter, NamedParameterJdbcOperations operations, @Nullable DataAccessStrategy accessStrategy) {
70+
71+
this.sqlGeneratorSource = sqlGeneratorSource;
72+
this.context = context;
73+
this.converter = converter;
74+
this.operations = operations;
75+
this.accessStrategy = accessStrategy == null ? this : accessStrategy;
76+
}
77+
6778
/**
6879
* Creates a {@link DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses.
6980
* Only suitable if this is the only access strategy in use.
7081
*/
7182
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
72-
RelationalConverter converter, NamedParameterJdbcOperations operations) {
83+
JdbcConverter converter, NamedParameterJdbcOperations operations) {
7384

74-
this.sqlGeneratorSource = sqlGeneratorSource;
75-
this.operations = operations;
76-
this.context = context;
77-
this.converter = converter;
78-
this.accessStrategy = this;
85+
this(sqlGeneratorSource, context, converter, operations, null);
7986
}
8087

8188
/*
@@ -97,28 +104,19 @@ public <T> Object insert(T instance, Class<T> domainType, Identifier identifier)
97104
KeyHolder holder = new GeneratedKeyHolder();
98105
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
99106

100-
Map<String, Object> parameters = new LinkedHashMap<>(identifier.size());
101-
identifier.forEach((name, value, type) -> {
102-
parameters.put(name, converter.writeValue(value, ClassTypeInformation.from(type)));
103-
});
107+
MapSqlParameterSource parameterSource = getParameterSource(instance, persistentEntity, "", PersistentProperty::isIdProperty);
104108

105-
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity, "");
109+
identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type));
106110

107111
Object idValue = getIdValueOrNull(instance, persistentEntity);
108-
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
109-
110112
if (idValue != null) {
111113

112-
Assert.notNull(idProperty, "Since we have a non-null idValue, we must have an idProperty as well.");
113-
114-
parameters.put(idProperty.getColumnName(),
115-
converter.writeValue(idValue, ClassTypeInformation.from(idProperty.getColumnType())));
114+
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
115+
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
116116
}
117117

118-
parameters.forEach(parameterSource::addValue);
119-
120118
operations.update( //
121-
sql(domainType).getInsert(parameters.keySet()), //
119+
sql(domainType).getInsert(new HashSet<>(Arrays.asList(parameterSource.getParameterNames()))), //
122120
parameterSource, //
123121
holder //
124122
);
@@ -135,7 +133,8 @@ public <S> boolean update(S instance, Class<S> domainType) {
135133

136134
RelationalPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType);
137135

138-
return operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity, "")) != 0;
136+
return operations.update(sql(domainType).getUpdate(),
137+
getParameterSource(instance, persistentEntity, "", property -> false)) != 0;
139138
}
140139

141140
/*
@@ -240,17 +239,14 @@ public <T> Iterable<T> findAll(Class<T> domainType) {
240239
@Override
241240
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
242241

243-
String findAllInListSql = sql(domainType).getFindAllInList();
244-
Class<?> targetType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
242+
RelationalPersistentProperty idProperty = getRequiredPersistentEntity(domainType).getRequiredIdProperty();
243+
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
245244

246-
MapSqlParameterSource parameter = new MapSqlParameterSource( //
247-
"ids", //
248-
StreamSupport.stream(ids.spliterator(), false) //
249-
.map(id -> converter.writeValue(id, ClassTypeInformation.from(targetType))) //
250-
.collect(Collectors.toList()) //
251-
);
245+
addConvertedPropertyValuesAsList(parameterSource, idProperty, ids, "ids");
246+
247+
String findAllInListSql = sql(domainType).getFindAllInList();
252248

253-
return operations.query(findAllInListSql, parameter, (RowMapper<T>) getEntityRowMapper(domainType));
249+
return operations.query(findAllInListSql, parameterSource, (RowMapper<T>) getEntityRowMapper(domainType));
254250
}
255251

256252
/*
@@ -292,15 +288,18 @@ public <T> boolean existsById(Object id, Class<T> domainType) {
292288
return result;
293289
}
294290

295-
private <S, T> MapSqlParameterSource getPropertyMap(S instance, RelationalPersistentEntity<S> persistentEntity,
296-
String prefix) {
291+
private <S, T> MapSqlParameterSource getParameterSource(S instance, RelationalPersistentEntity<S> persistentEntity,
292+
String prefix, Predicate<RelationalPersistentProperty> skipProperty) {
297293

298294
MapSqlParameterSource parameters = new MapSqlParameterSource();
299295

300296
PersistentPropertyAccessor<S> propertyAccessor = persistentEntity.getPropertyAccessor(instance);
301297

302298
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
303299

300+
if (skipProperty.test(property)) {
301+
return;
302+
}
304303
if (property.isEntity() && !property.isEmbedded()) {
305304
return;
306305
}
@@ -309,42 +308,21 @@ private <S, T> MapSqlParameterSource getPropertyMap(S instance, RelationalPersis
309308

310309
Object value = propertyAccessor.getProperty(property);
311310
RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
312-
MapSqlParameterSource additionalParameters = getPropertyMap((T) value,
313-
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix());
311+
MapSqlParameterSource additionalParameters = getParameterSource((T) value,
312+
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty);
314313
parameters.addValues(additionalParameters.getValues());
315314
} else {
316315

317316
Object value = propertyAccessor.getProperty(property);
318-
Object convertedValue = convertForWrite(property, value);
317+
String paramName = prefix + property.getColumnName();
319318

320-
parameters.addValue(prefix + property.getColumnName(), convertedValue,
321-
JdbcUtil.sqlTypeFor(property.getColumnType()));
319+
addConvertedPropertyValue(parameters, property, value, paramName);
322320
}
323321
});
324322

325323
return parameters;
326324
}
327325

328-
@Nullable
329-
private Object convertForWrite(RelationalPersistentProperty property, @Nullable Object value) {
330-
331-
Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType()));
332-
333-
if (convertedValue == null || !convertedValue.getClass().isArray()) {
334-
return convertedValue;
335-
}
336-
337-
Class<?> componentType = convertedValue.getClass();
338-
while (componentType.isArray()) {
339-
componentType = componentType.getComponentType();
340-
}
341-
342-
String typeName = JDBCType.valueOf(JdbcUtil.sqlTypeFor(componentType)).getName();
343-
344-
return operations.getJdbcOperations()
345-
.execute((Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue));
346-
}
347-
348326
@SuppressWarnings("unchecked")
349327
@Nullable
350328
private <S, ID> ID getIdValueOrNull(S instance, RelationalPersistentEntity<S> persistentEntity) {
@@ -398,8 +376,65 @@ private RowMapper<?> getMapEntityRowMapper(RelationalPersistentProperty property
398376

399377
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
400378

401-
Class<?> columnType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
402-
return new MapSqlParameterSource("id", converter.writeValue(id, ClassTypeInformation.from(columnType)));
379+
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
380+
381+
addConvertedPropertyValue( //
382+
parameterSource, //
383+
getRequiredPersistentEntity(domainType).getRequiredIdProperty(), //
384+
id, //
385+
"id" //
386+
);
387+
return parameterSource;
388+
}
389+
390+
private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, RelationalPersistentProperty property,
391+
Object value, String paramName) {
392+
393+
JdbcValue jdbcValue = converter.writeJdbcValue( //
394+
value, //
395+
property.getColumnType(), //
396+
property.getSqlType() //
397+
);
398+
399+
parameterSource.addValue(paramName, jdbcValue.getValue(), JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()));
400+
}
401+
402+
private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, String name, Object value,
403+
Class<?> type) {
404+
405+
JdbcValue jdbcValue = converter.writeJdbcValue( //
406+
value, //
407+
type, //
408+
JdbcUtil.sqlTypeFor(type) //
409+
);
410+
411+
parameterSource.addValue( //
412+
name, //
413+
jdbcValue.getValue(), //
414+
JdbcUtil.sqlTypeFor(jdbcValue.getJdbcType()) //
415+
);
416+
}
417+
418+
private void addConvertedPropertyValuesAsList(MapSqlParameterSource parameterSource,
419+
RelationalPersistentProperty property, Iterable<?> values, String paramName) {
420+
421+
List<Object> convertedIds = new ArrayList<>();
422+
JdbcValue jdbcValue = null;
423+
for (Object id : values) {
424+
425+
Class<?> columnType = property.getColumnType();
426+
int sqlType = property.getSqlType();
427+
428+
jdbcValue = converter.writeJdbcValue(id, columnType, sqlType);
429+
convertedIds.add(jdbcValue.getValue());
430+
}
431+
432+
Assert.notNull(jdbcValue, "JdbcValue must be not null at this point. Please report this as a bug.");
433+
434+
JDBCType jdbcType = jdbcValue.getJdbcType();
435+
int typeNumber = jdbcType == null ? JdbcUtils.TYPE_UNKNOWN : jdbcType.getVendorTypeNumber();
436+
437+
parameterSource.addValue(paramName, convertedIds, typeNumber);
403438
}
404439

405440
@SuppressWarnings("unchecked")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.core.convert;
17+
18+
import lombok.experimental.UtilityClass;
19+
20+
/**
21+
* A collection of utility methods for dealing with arrays.
22+
*
23+
* @author Jens Schauder
24+
*/
25+
@UtilityClass
26+
class ArrayUtil {
27+
28+
/**
29+
* Convertes an {@code Byte[]} into a {@code byte[]}
30+
* @param byteArray the array to be converted. Must not be {@literal null}.
31+
*
32+
* @return a {@code byte[]} of same size with the unboxed values of the input array. Guaranteed to be not {@literal null}.
33+
*/
34+
static Object toPrimitiveByteArray(Byte[] byteArray) {
35+
36+
byte[] bytes = new byte[byteArray.length];
37+
for (int i = 0; i < byteArray.length; i++) {
38+
bytes[i] = byteArray[i];
39+
}
40+
return bytes;
41+
}
42+
}

0 commit comments

Comments
 (0)