Skip to content

Commit fb858bf

Browse files
mp911deschauder
authored andcommitted
DATAJDBC-235 - Add support for configurable conversion.
We now support configurable conversion by introducing CustomConversions and RelationalConverter. CustomConversions is a registry for converters that should be applied on a per-type basis for properties. CustomConversions is typically registered as bean and fed into RelationalMappingContext and the newly introduced RelationalConverter to consider simple types and conversion rules. RelationalConverter with its implementation BasicRelationalConverter encapsulates conversion infrastructure such as EntityInstantiator, CustomConversions, and MappingContext that is required during relational value conversion. BasicRelationalConverter is responsible for simple value conversion and entity instantiation to pull related code together. It's not in full charge of row result to object mapping as this responsibility remains as part of DataAccessStrategy. This change supersedes and removes ConversionCustomizer.
1 parent 9ad0cf0 commit fb858bf

22 files changed

+833
-239
lines changed

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

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@
2727
import org.springframework.dao.EmptyResultDataAccessException;
2828
import org.springframework.dao.InvalidDataAccessApiUsageException;
2929
import org.springframework.dao.NonTransientDataAccessException;
30-
import org.springframework.data.convert.EntityInstantiators;
3130
import org.springframework.data.jdbc.support.JdbcUtil;
3231
import org.springframework.data.mapping.PersistentPropertyAccessor;
3332
import org.springframework.data.mapping.PropertyHandler;
3433
import org.springframework.data.mapping.PropertyPath;
35-
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
34+
import org.springframework.data.relational.core.conversion.RelationalConverter;
3635
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3736
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3837
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
38+
import org.springframework.data.util.ClassTypeInformation;
3939
import org.springframework.jdbc.core.RowMapper;
4040
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
4141
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@@ -48,6 +48,7 @@
4848
* The default {@link DataAccessStrategy} is to generate SQL statements based on meta data from the entity.
4949
*
5050
* @author Jens Schauder
51+
* @author Mark Paluch
5152
* @since 1.0
5253
*/
5354
@RequiredArgsConstructor
@@ -59,21 +60,21 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
5960

6061
private final @NonNull SqlGeneratorSource sqlGeneratorSource;
6162
private final @NonNull RelationalMappingContext context;
63+
private final @NonNull RelationalConverter converter;
6264
private final @NonNull NamedParameterJdbcOperations operations;
63-
private final @NonNull EntityInstantiators instantiators;
6465
private final @NonNull DataAccessStrategy accessStrategy;
6566

6667
/**
6768
* Creates a {@link DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses.
6869
* Only suitable if this is the only access strategy in use.
6970
*/
7071
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context,
71-
NamedParameterJdbcOperations operations, EntityInstantiators instantiators) {
72+
RelationalConverter converter, NamedParameterJdbcOperations operations) {
7273

7374
this.sqlGeneratorSource = sqlGeneratorSource;
7475
this.operations = operations;
7576
this.context = context;
76-
this.instantiators = instantiators;
77+
this.converter = converter;
7778
this.accessStrategy = this;
7879
}
7980

@@ -92,7 +93,8 @@ public <T> void insert(T instance, Class<T> domainType, Map<String, Object> addi
9293

9394
Assert.notNull(idProperty, "Since we have a non-null idValue, we must have an idProperty as well.");
9495

95-
additionalParameters.put(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType()));
96+
additionalParameters.put(idProperty.getColumnName(),
97+
converter.writeValue(idValue, ClassTypeInformation.from(idProperty.getColumnType())));
9698
}
9799

98100
additionalParameters.forEach(parameterSource::addValue);
@@ -231,7 +233,7 @@ public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) {
231233
MapSqlParameterSource parameter = new MapSqlParameterSource( //
232234
"ids", //
233235
StreamSupport.stream(ids.spliterator(), false) //
234-
.map(id -> convert(id, targetType)) //
236+
.map(id -> converter.writeValue(id, ClassTypeInformation.from(targetType))) //
235237
.collect(Collectors.toList()) //
236238
);
237239

@@ -281,11 +283,15 @@ private <S> MapSqlParameterSource getPropertyMap(final S instance, RelationalPer
281283

282284
MapSqlParameterSource parameters = new MapSqlParameterSource();
283285

286+
PersistentPropertyAccessor<S> propertyAccessor = persistentEntity.getPropertyAccessor(instance);
287+
284288
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
289+
285290
if (!property.isEntity()) {
286-
Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
287291

288-
Object convertedValue = convert(value, property.getColumnType());
292+
Object value = propertyAccessor.getProperty(property);
293+
294+
Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType()));
289295
parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
290296
}
291297
});
@@ -318,12 +324,10 @@ private <S> void setIdFromJdbc(S instance, KeyHolder holder, RelationalPersisten
318324

319325
getIdFromHolder(holder, persistentEntity).ifPresent(it -> {
320326

321-
PersistentPropertyAccessor accessor = persistentEntity.getPropertyAccessor(instance);
322-
ConvertingPropertyAccessor convertingPropertyAccessor = new ConvertingPropertyAccessor(accessor,
323-
context.getConversions());
327+
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(persistentEntity, instance);
324328
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
325329

326-
convertingPropertyAccessor.setProperty(idProperty, it);
330+
accessor.setProperty(idProperty, it);
327331
});
328332

329333
} catch (NonTransientDataAccessException e) {
@@ -344,7 +348,7 @@ private <S> Optional<Object> getIdFromHolder(KeyHolder holder, RelationalPersist
344348
}
345349

346350
public EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
347-
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, instantiators, accessStrategy);
351+
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, converter, accessStrategy);
348352
}
349353

350354
@SuppressWarnings("unchecked")
@@ -360,28 +364,14 @@ private RowMapper<?> getMapEntityRowMapper(RelationalPersistentProperty property
360364
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) {
361365

362366
Class<?> columnType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
363-
return new MapSqlParameterSource("id", convert(id, columnType));
367+
return new MapSqlParameterSource("id", converter.writeValue(id, ClassTypeInformation.from(columnType)));
364368
}
365369

366370
@SuppressWarnings("unchecked")
367371
private <S> RelationalPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) {
368372
return (RelationalPersistentEntity<S>) context.getRequiredPersistentEntity(domainType);
369373
}
370374

371-
@Nullable
372-
private <V> V convert(@Nullable Object from, Class<V> to) {
373-
374-
if (from == null) {
375-
return null;
376-
}
377-
378-
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(from.getClass());
379-
380-
Object id = persistentEntity == null ? null : persistentEntity.getIdentifierAccessor(from).getIdentifier();
381-
382-
return context.getConversions().convert(id == null ? from : id, to);
383-
}
384-
385375
private SqlGenerator sql(Class<?> domainType) {
386376
return sqlGeneratorSource.getSqlGenerator(domainType);
387377
}

src/main/java/org/springframework/data/jdbc/core/EntityRowMapper.java

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,13 @@
2222
import java.sql.SQLException;
2323
import java.util.Map;
2424

25-
import org.springframework.core.convert.ConversionService;
2625
import org.springframework.core.convert.converter.Converter;
27-
import org.springframework.data.convert.EntityInstantiators;
2826
import org.springframework.data.mapping.MappingException;
2927
import org.springframework.data.mapping.PersistentProperty;
3028
import org.springframework.data.mapping.PersistentPropertyAccessor;
3129
import org.springframework.data.mapping.PreferredConstructor.Parameter;
32-
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
3330
import org.springframework.data.mapping.model.ParameterValueProvider;
31+
import org.springframework.data.relational.core.conversion.RelationalConverter;
3432
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3533
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3634
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -39,9 +37,8 @@
3937
import org.springframework.util.Assert;
4038

4139
/**
42-
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced.
43-
*
44-
* This {@link RowMapper} might trigger additional SQL statements in order to load other members of the same aggregate.
40+
* Maps a {@link ResultSet} to an entity of type {@code T}, including entities referenced. This {@link RowMapper} might
41+
* trigger additional SQL statements in order to load other members of the same aggregate.
4542
*
4643
* @author Jens Schauder
4744
* @author Oliver Gierke
@@ -54,19 +51,17 @@ public class EntityRowMapper<T> implements RowMapper<T> {
5451

5552
private final RelationalPersistentEntity<T> entity;
5653

57-
private final ConversionService conversions;
54+
private final RelationalConverter converter;
5855
private final RelationalMappingContext context;
5956
private final DataAccessStrategy accessStrategy;
6057
private final RelationalPersistentProperty idProperty;
61-
private final EntityInstantiators instantiators;
6258

63-
public EntityRowMapper(RelationalPersistentEntity<T> entity, RelationalMappingContext context, EntityInstantiators instantiators,
64-
DataAccessStrategy accessStrategy) {
59+
public EntityRowMapper(RelationalPersistentEntity<T> entity, RelationalMappingContext context,
60+
RelationalConverter converter, DataAccessStrategy accessStrategy) {
6561

6662
this.entity = entity;
67-
this.conversions = context.getConversions();
63+
this.converter = converter;
6864
this.context = context;
69-
this.instantiators = instantiators;
7065
this.accessStrategy = accessStrategy;
7166
this.idProperty = entity.getIdProperty();
7267
}
@@ -80,8 +75,7 @@ public T mapRow(ResultSet resultSet, int rowNumber) {
8075

8176
T result = createInstance(entity, resultSet, "");
8277

83-
ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(result),
84-
conversions);
78+
PersistentPropertyAccessor<T> propertyAccessor = converter.getPropertyAccessor(entity, result);
8579

8680
Object id = idProperty == null ? null : readFrom(resultSet, idProperty, "");
8781

@@ -118,7 +112,7 @@ private Object readFrom(ResultSet resultSet, RelationalPersistentProperty proper
118112
return readEntityFrom(resultSet, property);
119113
}
120114

121-
return resultSet.getObject(prefix + property.getColumnName());
115+
return converter.readValue(resultSet.getObject(prefix + property.getColumnName()), property.getTypeInformation());
122116

123117
} catch (SQLException o_O) {
124118
throw new MappingException(String.format("Could not read property %s from result set!", property), o_O);
@@ -138,37 +132,33 @@ private <S> S readEntityFrom(ResultSet rs, PersistentProperty<?> property) {
138132
return null;
139133
}
140134

141-
S instance =
142-
createInstance(entity, rs, prefix);
135+
S instance = createInstance(entity, rs, prefix);
143136

144-
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance);
145-
ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions);
137+
PersistentPropertyAccessor<S> accessor = converter.getPropertyAccessor(entity, instance);
146138

147139
for (RelationalPersistentProperty p : entity) {
148-
propertyAccessor.setProperty(p, readFrom(rs, p, prefix));
140+
accessor.setProperty(p, readFrom(rs, p, prefix));
149141
}
150142

151143
return instance;
152144
}
153145

154146
private <S> S createInstance(RelationalPersistentEntity<S> entity, ResultSet rs, String prefix) {
155-
156-
return instantiators.getInstantiatorFor(entity) //
157-
.createInstance(entity, new ResultSetParameterValueProvider(rs, entity, conversions, prefix));
147+
return converter.createInstance(entity, new ResultSetParameterValueProvider(rs, entity, prefix));
158148
}
159149

160150
@RequiredArgsConstructor
161151
private static class ResultSetParameterValueProvider implements ParameterValueProvider<RelationalPersistentProperty> {
162152

163153
@NonNull private final ResultSet resultSet;
164154
@NonNull private final RelationalPersistentEntity<?> entity;
165-
@NonNull private final ConversionService conversionService;
166155
@NonNull private final String prefix;
167156

168157
/*
169158
* (non-Javadoc)
170159
* @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter)
171160
*/
161+
@SuppressWarnings("unchecked")
172162
@Override
173163
public <T> T getParameterValue(Parameter<T, RelationalPersistentProperty> parameter) {
174164

@@ -177,7 +167,7 @@ public <T> T getParameterValue(Parameter<T, RelationalPersistentProperty> parame
177167
String column = prefix + entity.getRequiredPersistentProperty(parameterName).getColumnName();
178168

179169
try {
180-
return conversionService.convert(resultSet.getObject(column), parameter.getType().getType());
170+
return (T) resultSet.getObject(column);
181171
} catch (SQLException o_O) {
182172
throw new MappingException(String.format("Couldn't read column %s from ResultSet.", column), o_O);
183173
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2018 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 java.util.Collections;
19+
import java.util.List;
20+
21+
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
22+
23+
/**
24+
* Value object to capture custom conversion. {@link JdbcCustomConversions} also act as factory for
25+
* {@link org.springframework.data.mapping.model.SimpleTypeHolder}
26+
*
27+
* @author Mark Paluch
28+
* @see org.springframework.data.convert.CustomConversions
29+
* @see org.springframework.data.mapping.model.SimpleTypeHolder
30+
* @see JdbcSimpleTypes
31+
*/
32+
public class JdbcCustomConversions extends org.springframework.data.convert.CustomConversions {
33+
34+
private static final StoreConversions STORE_CONVERSIONS;
35+
private static final List<Object> STORE_CONVERTERS;
36+
37+
static {
38+
39+
STORE_CONVERTERS = Collections.emptyList();
40+
STORE_CONVERSIONS = StoreConversions.of(JdbcSimpleTypes.HOLDER, STORE_CONVERTERS);
41+
}
42+
43+
/**
44+
* Creates an empty {@link JdbcCustomConversions} object.
45+
*/
46+
public JdbcCustomConversions() {
47+
this(Collections.emptyList());
48+
}
49+
50+
/**
51+
* Create a new {@link JdbcCustomConversions} instance registering the given converters.
52+
*
53+
* @param converters must not be {@literal null}.
54+
*/
55+
public JdbcCustomConversions(List<?> converters) {
56+
super(STORE_CONVERSIONS, converters);
57+
}
58+
59+
}

0 commit comments

Comments
 (0)