Skip to content

Commit 4afac46

Browse files
committed
DATAJDBC-327 - Adds support for conversion to JdbcTypeAware and storing 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. See also: DATAJDBC-239.
1 parent f871441 commit 4afac46

File tree

35 files changed

+714
-128
lines changed

35 files changed

+714
-128
lines changed

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

Lines changed: 101 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,25 @@
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.HashSet;
2425
import java.util.LinkedHashMap;
26+
import java.util.List;
2527
import java.util.Map;
26-
import java.util.stream.Collectors;
27-
import java.util.stream.StreamSupport;
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.JdbcTypeAware;
3234
import org.springframework.data.jdbc.support.JdbcUtil;
3335
import org.springframework.data.mapping.PersistentPropertyAccessor;
3436
import org.springframework.data.mapping.PersistentPropertyPath;
3537
import org.springframework.data.mapping.PropertyHandler;
36-
import org.springframework.data.relational.core.conversion.RelationalConverter;
3738
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3839
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3940
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -43,6 +44,7 @@
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, "", true);
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, "", 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, boolean skipId) {
297293

298294
MapSqlParameterSource parameters = new MapSqlParameterSource();
299295

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

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

300+
if (skipId && property.isIdProperty()) {
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(), skipId);
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+
JdbcTypeAware jdbcTypeAware = converter.writeTypeAware( //
394+
value, //
395+
property.getColumnType(), //
396+
property.getSqlType() //
397+
);
398+
399+
parameterSource.addValue(paramName, jdbcTypeAware.getValue(), JdbcUtil.sqlTypeFor(jdbcTypeAware.getJdbcType()));
400+
}
401+
402+
private void addConvertedPropertyValue(MapSqlParameterSource parameterSource, String name, Object value,
403+
Class<?> type) {
404+
405+
JdbcTypeAware jdbcTypeAware = converter.writeTypeAware( //
406+
value, //
407+
type, //
408+
JdbcUtil.sqlTypeFor(type) //
409+
);
410+
411+
parameterSource.addValue( //
412+
name, //
413+
jdbcTypeAware.getValue(), //
414+
JdbcUtil.sqlTypeFor(jdbcTypeAware.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+
JdbcTypeAware jdbcTypeAware = null;
423+
for (Object id : values) {
424+
425+
Class<?> columnType = property.getColumnType();
426+
int sqlType = property.getSqlType();
427+
428+
jdbcTypeAware = converter.writeTypeAware(id, columnType, sqlType);
429+
convertedIds.add(jdbcTypeAware.getValue());
430+
}
431+
432+
Assert.notNull(jdbcTypeAware, "JdbcTypeAware must be not null at this point. Please report this as a bug.");
433+
434+
JDBCType jdbcType = jdbcTypeAware.getJdbcType();
435+
int typeNumber = jdbcType == null ? JdbcUtils.TYPE_UNKNOWN : jdbcType.getVendorTypeNumber();
436+
437+
parameterSource.addValue(paramName, convertedIds, typeNumber);
403438
}
404439

405440
@SuppressWarnings("unchecked")

0 commit comments

Comments
 (0)