Skip to content

Commit ade3324

Browse files
schaudermp911de
authored andcommitted
DATAJDBC-326 - Support conversion of backreferences.
Ids used as backreferences now get properly converted. Introduced Identifier to hold information about data that needs to be considered for inserts or updates but is not part of the entity. Apart from column names and values they also hold information about the desired JdbcType in order to facilitate conversions. This replaces the Map handed around in the past. Original pull request: #118.
1 parent 8eb80ba commit ade3324

File tree

24 files changed

+610
-95
lines changed

24 files changed

+610
-95
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.function.Consumer;
2222
import java.util.function.Function;
2323

24+
import org.springframework.data.relational.domain.Identifier;
2425
import org.springframework.data.mapping.PersistentPropertyPath;
2526
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
2627

@@ -47,6 +48,15 @@ public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> ad
4748
return collect(das -> das.insert(instance, domainType, additionalParameters));
4849
}
4950

51+
/*
52+
* (non-Javadoc)
53+
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, org.springframework.data.jdbc.core.ParentKeys)
54+
*/
55+
@Override
56+
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
57+
return collect(das -> das.insert(instance, domainType, identifier));
58+
}
59+
5060
/*
5161
* (non-Javadoc)
5262
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
@@ -149,7 +159,7 @@ public <T> boolean existsById(Object id, Class<T> domainType) {
149159
private <T> T collect(Function<DataAccessStrategy, T> function) {
150160

151161
// Keep <T> as Eclipse fails to compile if <> is used.
152-
return strategies.stream().collect(new FunctionCollector<T>(function));
162+
return strategies.stream().collect(new FunctionCollector<>(function));
153163
}
154164

155165
private void collectVoid(Consumer<DataAccessStrategy> consumer) {

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Map;
1919

20+
import org.springframework.data.relational.domain.Identifier;
2021
import org.springframework.data.mapping.PersistentPropertyPath;
2122
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
2223
import org.springframework.lang.Nullable;
@@ -39,9 +40,27 @@ public interface DataAccessStrategy {
3940
* to get referenced are contained in this map. Must not be {@code null}.
4041
* @param <T> the type of the instance.
4142
* @return the id generated by the database if any.
43+
*
44+
* @deprecated use {@link #insert(Object, Class, Identifier)} instead.
4245
*/
46+
@Deprecated
4347
<T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
4448

49+
50+
/**
51+
* Inserts a the data of a single entity. Referenced entities don't get handled.
52+
*
53+
* @param instance the instance to be stored. Must not be {@code null}.
54+
* @param domainType the type of the instance. Must not be {@code null}.
55+
* @param identifier information about data that needs to be considered for the insert but which is not part of the entity.
56+
* Namely references back to a parent entity and key/index columns for entities that are stored in a {@link Map} or {@link java.util.List}.
57+
* @param <T> the type of the instance.
58+
* @return the id generated by the database if any.
59+
*/
60+
default <T> Object insert(T instance, Class<T> domainType, Identifier identifier){
61+
return insert(instance, domainType, identifier.getParametersByName());
62+
}
63+
4564
/**
4665
* Updates the data of a single entity in the database. Referenced entities don't get handled.
4766
*

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

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
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.JdbcIdentifierBuilder;
3233
import org.springframework.data.jdbc.support.JdbcUtil;
3334
import org.springframework.data.mapping.PersistentPropertyAccessor;
3435
import org.springframework.data.mapping.PersistentPropertyPath;
@@ -37,6 +38,7 @@
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;
41+
import org.springframework.data.relational.domain.Identifier;
4042
import org.springframework.data.util.ClassTypeInformation;
4143
import org.springframework.jdbc.core.RowMapper;
4244
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@@ -87,10 +89,24 @@ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, Relation
8789
*/
8890
@Override
8991
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
92+
return insert(instance, domainType, JdbcIdentifierBuilder.from(additionalParameters).build());
93+
}
94+
95+
/*
96+
* (non-Javadoc)
97+
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
98+
*/
99+
@Override
100+
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
90101

91102
KeyHolder holder = new GeneratedKeyHolder();
92103
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
93-
Map<String, Object> parameters = new LinkedHashMap<>(additionalParameters);
104+
105+
Map<String, Object> parameters = new LinkedHashMap<>();
106+
identifier.forEach(identifierValue -> {
107+
parameters.put(identifierValue.getName(),
108+
converter.writeValue(identifierValue.getValue(), ClassTypeInformation.from(identifierValue.getTargetType())));
109+
});
94110

95111
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity, "");
96112

@@ -282,7 +298,8 @@ public <T> boolean existsById(Object id, Class<T> domainType) {
282298
return result;
283299
}
284300

285-
private <S, T> MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity<S> persistentEntity, String prefix) {
301+
private <S, T> MapSqlParameterSource getPropertyMap(final S instance, RelationalPersistentEntity<S> persistentEntity,
302+
String prefix) {
286303

287304
MapSqlParameterSource parameters = new MapSqlParameterSource();
288305

@@ -294,23 +311,26 @@ private <S, T> MapSqlParameterSource getPropertyMap(final S instance, Relational
294311
return;
295312
}
296313

297-
if(property.isEmbedded()){
314+
if (property.isEmbedded()) {
298315

299316
Object value = propertyAccessor.getProperty(property);
300-
final RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
301-
final MapSqlParameterSource additionalParameters = getPropertyMap((T)value, (RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix());
317+
final RelationalPersistentEntity<?> embeddedEntity = context.getPersistentEntity(property.getType());
318+
final MapSqlParameterSource additionalParameters = getPropertyMap((T) value,
319+
(RelationalPersistentEntity<T>) embeddedEntity, prefix + property.getEmbeddedPrefix());
302320
parameters.addValues(additionalParameters.getValues());
303321
} else {
304322

305323
Object value = propertyAccessor.getProperty(property);
306324
Object convertedValue = convertForWrite(property, value);
307325

308-
parameters.addValue(prefix + property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
326+
parameters.addValue(prefix + property.getColumnName(), convertedValue,
327+
JdbcUtil.sqlTypeFor(property.getColumnType()));
309328
}
310329
});
311330

312331
return parameters;
313332
}
333+
314334
@Nullable
315335
private Object convertForWrite(RelationalPersistentProperty property, @Nullable Object value) {
316336

@@ -327,9 +347,8 @@ private Object convertForWrite(RelationalPersistentProperty property, @Nullable
327347

328348
String typeName = JDBCType.valueOf(JdbcUtil.sqlTypeFor(componentType)).getName();
329349

330-
return operations.getJdbcOperations().execute(
331-
(Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue)
332-
);
350+
return operations.getJdbcOperations()
351+
.execute((Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue));
333352
}
334353

335354
@SuppressWarnings("unchecked")
@@ -354,22 +373,22 @@ private static <S, ID> boolean isIdPropertyNullOrScalarZero(@Nullable ID idValue
354373
@Nullable
355374
private <S> Object getIdFromHolder(KeyHolder holder, RelationalPersistentEntity<S> persistentEntity) {
356375

357-
try {
358-
// MySQL just returns one value with a special name
359-
return holder.getKey();
360-
} catch (DataRetrievalFailureException | InvalidDataAccessApiUsageException e) {
361-
// Postgres returns a value for each column
376+
try {
377+
// MySQL just returns one value with a special name
378+
return holder.getKey();
379+
} catch (DataRetrievalFailureException | InvalidDataAccessApiUsageException e) {
380+
// Postgres returns a value for each column
362381
// MS SQL Server returns a value that might be null.
363382

364-
Map<String, Object> keys = holder.getKeys();
383+
Map<String, Object> keys = holder.getKeys();
365384

366-
if (keys == null || persistentEntity.getIdProperty() == null) {
367-
return null;
368-
}
385+
if (keys == null || persistentEntity.getIdProperty() == null) {
386+
return null;
387+
}
369388

370-
return keys.get(persistentEntity.getIdColumn());
371-
}
372-
}
389+
return keys.get(persistentEntity.getIdColumn());
390+
}
391+
}
373392

374393
private EntityRowMapper<?> getEntityRowMapper(Class<?> domainType) {
375394
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), context, converter, accessStrategy);

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

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818
import lombok.RequiredArgsConstructor;
1919

2020
import java.util.Collections;
21-
import java.util.HashMap;
2221
import java.util.Map;
2322

23+
import org.springframework.data.jdbc.core.convert.JdbcIdentifierBuilder;
24+
import org.springframework.data.relational.domain.Identifier;
2425
import org.springframework.data.mapping.PersistentPropertyPath;
2526
import org.springframework.data.relational.core.conversion.DbAction;
2627
import org.springframework.data.relational.core.conversion.DbAction.Delete;
@@ -58,7 +59,7 @@ class DefaultJdbcInterpreter implements Interpreter {
5859
@Override
5960
public <T> void interpret(Insert<T> insert) {
6061

61-
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), createAdditionalColumnValues(insert));
62+
Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), getParentKeys(insert));
6263

6364
insert.setGeneratedId(id);
6465
}
@@ -101,7 +102,7 @@ public <T> void interpret(Merge<T> merge) {
101102

102103
// temporary implementation
103104
if (!accessStrategy.update(merge.getEntity(), merge.getEntityType())) {
104-
accessStrategy.insert(merge.getEntity(), merge.getEntityType(), createAdditionalColumnValues(merge));
105+
accessStrategy.insert(merge.getEntity(), merge.getEntityType(), getParentKeys(merge));
105106
}
106107
}
107108

@@ -141,27 +142,21 @@ public <T> void interpret(DeleteAllRoot<T> deleteAllRoot) {
141142
accessStrategy.deleteAll(deleteAllRoot.getEntityType());
142143
}
143144

144-
private <T> Map<String, Object> createAdditionalColumnValues(DbAction.WithDependingOn<T> action) {
145-
146-
Map<String, Object> additionalColumnValues = new HashMap<>();
147-
addDependingOnInformation(action, additionalColumnValues);
148-
additionalColumnValues.putAll(action.getAdditionalValues());
149-
150-
return additionalColumnValues;
151-
}
152-
153-
private <T> void addDependingOnInformation(DbAction.WithDependingOn<T> action,
154-
Map<String, Object> additionalColumnValues) {
145+
private <T> Identifier getParentKeys(DbAction.WithDependingOn<?> action) {
155146

156147
DbAction.WithEntity<?> dependingOn = action.getDependingOn();
157148

158149
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(dependingOn.getEntityType());
159150

160-
String columnName = getColumnNameForReverseColumn(action);
151+
Object id = getIdFromEntityDependingOn(dependingOn, persistentEntity);
152+
JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder //
153+
.forBackReferences(action.getPropertyPath(), id);
161154

162-
Object identifier = getIdFromEntityDependingOn(dependingOn, persistentEntity);
155+
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers().entrySet()) {
156+
identifier = identifier.withQualifier(qualifier.getKey(), qualifier.getValue());
157+
}
163158

164-
additionalColumnValues.put(columnName, identifier);
159+
return identifier.build();
165160
}
166161

167162
@Nullable
@@ -182,9 +177,4 @@ private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn,
182177
return persistentEntity.getIdentifierAccessor(entity).getIdentifier();
183178
}
184179

185-
private String getColumnNameForReverseColumn(DbAction.WithPropertyPath<?> action) {
186-
187-
PersistentPropertyPath<RelationalPersistentProperty> path = action.getPropertyPath();
188-
return path.getRequiredLeafProperty().getReverseColumnName();
189-
}
190180
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Map;
1919

20+
import org.springframework.data.relational.domain.Identifier;
2021
import org.springframework.data.mapping.PersistentPropertyPath;
2122
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
2223
import org.springframework.util.Assert;
@@ -40,6 +41,15 @@ public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> ad
4041
return delegate.insert(instance, domainType, additionalParameters);
4142
}
4243

44+
/*
45+
* (non-Javadoc)
46+
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, org.springframework.data.jdbc.core.ParentKeys)
47+
*/
48+
@Override
49+
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier) {
50+
return delegate.insert(instance, domainType, identifier);
51+
}
52+
4353
/*
4454
* (non-Javadoc)
4555
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,23 @@
2020
import org.springframework.core.convert.ConverterNotFoundException;
2121
import org.springframework.data.convert.CustomConversions;
2222
import org.springframework.data.jdbc.core.mapping.AggregateReference;
23+
import org.springframework.data.mapping.PersistentPropertyPath;
2324
import org.springframework.data.mapping.context.MappingContext;
2425
import org.springframework.data.mapping.model.SimpleTypeHolder;
2526
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
2627
import org.springframework.data.relational.core.conversion.RelationalConverter;
2728
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
2829
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
30+
import org.springframework.data.relational.domain.Identifier;
2931
import org.springframework.data.util.TypeInformation;
3032
import org.springframework.lang.Nullable;
3133

3234
import java.sql.Array;
3335
import java.sql.SQLException;
36+
import java.util.ArrayList;
37+
import java.util.Collections;
38+
import java.util.List;
39+
import java.util.Map;
3440

3541
/**
3642
* {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to
@@ -122,4 +128,6 @@ public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
122128

123129
return super.writeValue(value, type);
124130
}
131+
132+
125133
}

0 commit comments

Comments
 (0)