Skip to content

Commit cfd7895

Browse files
committed
DATAJDBC-326 - Support conversion of backreferences.
Ids used as backreferences now get properly converted. Introduced ParentKeys. ParentKeys hold information about data that needs to be considered for inserts or updates but is not part of the entity. Appart 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.
1 parent be74f4f commit cfd7895

File tree

20 files changed

+434
-70
lines changed

20 files changed

+434
-70
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> ad
4747
return collect(das -> das.insert(instance, domainType, additionalParameters));
4848
}
4949

50+
/*
51+
* (non-Javadoc)
52+
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, org.springframework.data.jdbc.core.ParentKeys)
53+
*/
54+
@Override
55+
public <T> Object insert(T instance, Class<T> domainType, ParentKeys parentKeys) {
56+
return collect(das -> das.insert(instance, domainType, parentKeys));
57+
}
58+
5059
/*
5160
* (non-Javadoc)
5261
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
@@ -149,7 +158,7 @@ public <T> boolean existsById(Object id, Class<T> domainType) {
149158
private <T> T collect(Function<DataAccessStrategy, T> function) {
150159

151160
// Keep <T> as Eclipse fails to compile if <> is used.
152-
return strategies.stream().collect(new FunctionCollector<T>(function));
161+
return strategies.stream().collect(new FunctionCollector<>(function));
153162
}
154163

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

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,27 @@ public interface DataAccessStrategy {
3939
* to get referenced are contained in this map. Must not be {@code null}.
4040
* @param <T> the type of the instance.
4141
* @return the id generated by the database if any.
42+
*
43+
* @deprecated use {@link #insert(Object, Class, ParentKeys)} instead.
4244
*/
45+
@Deprecated
4346
<T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters);
4447

48+
49+
/**
50+
* Inserts a the data of a single entity. Referenced entities don't get handled.
51+
*
52+
* @param instance the instance to be stored. Must not be {@code null}.
53+
* @param domainType the type of the instance. Must not be {@code null}.
54+
* @param parentKeys information about data that needs to be considered for the insert but which is not part of the entity.
55+
* 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}.
56+
* @param <T> the type of the instance.
57+
* @return the id generated by the database if any.
58+
*/
59+
default <T> Object insert(T instance, Class<T> domainType, ParentKeys parentKeys){
60+
return insert(instance, domainType, parentKeys.getParametersByName());
61+
}
62+
4563
/**
4664
* Updates the data of a single entity in the database. Referenced entities don't get handled.
4765
*

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,23 @@ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, Relation
8787
*/
8888
@Override
8989
public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) {
90+
return insert(instance, domainType, ParentKeys.fromNamedValues(additionalParameters));
91+
}
92+
93+
/*
94+
* (non-Javadoc)
95+
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, java.util.Map)
96+
*/
97+
@Override
98+
public <T> Object insert(T instance, Class<T> domainType, ParentKeys parentKeys) {
9099

91100
KeyHolder holder = new GeneratedKeyHolder();
92101
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
93-
Map<String, Object> parameters = new LinkedHashMap<>(additionalParameters);
102+
103+
Map<String, Object> parameters = new LinkedHashMap<>();
104+
for (ParentKeys.ParentKey parameter : parentKeys.getParameters()) {
105+
parameters.put(parameter.getName(), converter.writeValue(parameter.getValue(), ClassTypeInformation.from(parameter.getTargetType())));
106+
}
94107

95108
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity, "");
96109

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
@@ -16,9 +16,9 @@
1616
package org.springframework.data.jdbc.core;
1717

1818
import lombok.RequiredArgsConstructor;
19+
import lombok.val;
1920

2021
import java.util.Collections;
21-
import java.util.HashMap;
2222
import java.util.Map;
2323

2424
import org.springframework.data.mapping.PersistentPropertyPath;
@@ -37,6 +37,7 @@
3737
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3838
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
3939
import org.springframework.lang.Nullable;
40+
import org.springframework.util.Assert;
4041

4142
/**
4243
* {@link Interpreter} for {@link DbAction}s using a {@link DataAccessStrategy} for performing actual database
@@ -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> ParentKeys 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);
161-
162151
Object identifier = getIdFromEntityDependingOn(dependingOn, persistentEntity);
152+
ParentKeys parentKeys = ParentKeys //
153+
.forBackReferences(action.getPropertyPath(), identifier);
163154

164-
additionalColumnValues.put(columnName, identifier);
155+
for (Map.Entry<PersistentPropertyPath<RelationalPersistentProperty>, Object> qualifier : action.getQualifiers().entrySet()) {
156+
parentKeys = parentKeys.withQualifier(qualifier.getKey(), qualifier.getValue());
157+
}
158+
159+
return parentKeys;
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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> ad
4040
return delegate.insert(instance, domainType, additionalParameters);
4141
}
4242

43+
/*
44+
* (non-Javadoc)
45+
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, org.springframework.data.jdbc.core.ParentKeys)
46+
*/
47+
@Override
48+
public <T> Object insert(T instance, Class<T> domainType, ParentKeys parentKeys) {
49+
return delegate.insert(instance, domainType, parentKeys);
50+
}
51+
4352
/*
4453
* (non-Javadoc)
4554
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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;
17+
18+
import lombok.Value;
19+
20+
import java.util.ArrayList;
21+
import java.util.Collection;
22+
import java.util.Collections;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
27+
import org.springframework.data.mapping.PersistentPropertyPath;
28+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
29+
import org.springframework.lang.Nullable;
30+
31+
/**
32+
* @author Jens Schauder
33+
*/
34+
public class ParentKeys {
35+
36+
private final List<ParentKey> keys;
37+
38+
/**
39+
* Only provided as a bridge to the old way of passing additional parameters to an insert statement.
40+
*
41+
* @param additionalParameters
42+
*/
43+
@Deprecated
44+
static ParentKeys fromNamedValues(Map<String, Object> additionalParameters) {
45+
46+
List<ParentKey> keys = new ArrayList<>();
47+
additionalParameters.forEach((k, v) -> keys.add(new ParentKey(k, v, v == null ? Object.class : v.getClass())));
48+
49+
ParentKeys parentKeys = new ParentKeys(keys);
50+
return parentKeys;
51+
}
52+
53+
/**
54+
* Creates ParentKeys with backreference for the given path and value of the parents id.
55+
*/
56+
static ParentKeys forBackReferences(PersistentPropertyPath<RelationalPersistentProperty> path, @Nullable Object value) {
57+
58+
ParentKey parentKey = new ParentKey( //
59+
path.getRequiredLeafProperty().getReverseColumnName(), //
60+
value, //
61+
getLastIdProperty(path).getColumnType() //
62+
);
63+
64+
return new ParentKeys(Collections.singletonList(parentKey));
65+
}
66+
67+
private static RelationalPersistentProperty getLastIdProperty(
68+
PersistentPropertyPath<RelationalPersistentProperty> path) {
69+
70+
RelationalPersistentProperty idProperty = path.getRequiredLeafProperty().getOwner().getIdProperty();
71+
72+
if (idProperty != null) {
73+
return idProperty;
74+
}
75+
76+
return getLastIdProperty(path.getParentPath());
77+
}
78+
79+
private ParentKeys(List<ParentKey> keys) {
80+
81+
this.keys = Collections.unmodifiableList(keys);
82+
}
83+
84+
ParentKeys withQualifier(PersistentPropertyPath<RelationalPersistentProperty> path, Object value) {
85+
86+
List<ParentKey> keys = new ArrayList<>(this.keys);
87+
88+
RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty();
89+
keys.add(new ParentKey(leafProperty.getKeyColumn(), value, leafProperty.getQualifierColumnType()));
90+
91+
return new ParentKeys(keys);
92+
}
93+
94+
@Deprecated
95+
public Map<String, Object> getParametersByName() {
96+
return new HashMap<>();
97+
}
98+
99+
Collection<ParentKey> getParameters() {
100+
return keys;
101+
}
102+
103+
@Value
104+
static class ParentKey {
105+
String name;
106+
Object value;
107+
Class<?> targetType;
108+
}
109+
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.data.jdbc.core.DataAccessStrategy;
2727
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
2828
import org.springframework.data.jdbc.core.DelegatingDataAccessStrategy;
29+
import org.springframework.data.jdbc.core.ParentKeys;
2930
import org.springframework.data.jdbc.core.SqlGeneratorSource;
3031
import org.springframework.data.mapping.PersistentPropertyPath;
3132
import org.springframework.data.mapping.PropertyPath;
@@ -136,6 +137,19 @@ public <T> Object insert(T instance, Class<T> domainType, Map<String, Object> ad
136137
return myBatisContext.getId();
137138
}
138139

140+
/*
141+
* (non-Javadoc)
142+
* @see org.springframework.data.jdbc.core.DataAccessStrategy#insert(java.lang.Object, java.lang.Class, ParentKeys)
143+
*/
144+
@Override
145+
public <T> Object insert(T instance, Class<T> domainType, ParentKeys parentKeys) {
146+
147+
MyBatisContext myBatisContext = new MyBatisContext(null, instance, domainType, parentKeys.getParametersByName());
148+
sqlSession().insert(namespace(domainType) + ".insert", myBatisContext);
149+
150+
return myBatisContext.getId();
151+
}
152+
139153
/*
140154
* (non-Javadoc)
141155
* @see org.springframework.data.jdbc.core.DataAccessStrategy#update(java.lang.Object, java.lang.Class)

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.junit.Test;
2626
import org.mockito.ArgumentCaptor;
2727
import org.springframework.data.annotation.Id;
28+
import org.springframework.data.jdbc.core.ParentKeys.ParentKey;
2829
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
2930
import org.springframework.data.relational.core.conversion.DbAction.Insert;
3031
import org.springframework.data.relational.core.conversion.DbAction.InsertRoot;
@@ -67,10 +68,10 @@ public void insertDoesHonourNamingStrategyForBackReference() {
6768

6869
interpreter.interpret(insert);
6970

70-
ArgumentCaptor<Map<String, Object>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
71+
ArgumentCaptor<ParentKeys> argumentCaptor = ArgumentCaptor.forClass(ParentKeys.class);
7172
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
7273

73-
assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID));
74+
assertThat(argumentCaptor.getValue().getParameters()).containsExactly(new ParentKey(BACK_REFERENCE, CONTAINER_ID, Long.class));
7475
}
7576

7677
@Test // DATAJDBC-251
@@ -80,10 +81,10 @@ public void idOfParentGetsPassedOnAsAdditionalParameterIfNoIdGotGenerated() {
8081

8182
interpreter.interpret(insert);
8283

83-
ArgumentCaptor<Map<String, Object>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
84+
ArgumentCaptor<ParentKeys> argumentCaptor = ArgumentCaptor.forClass(ParentKeys.class);
8485
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
8586

86-
assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID));
87+
assertThat(argumentCaptor.getValue().getParameters()).containsExactly(new ParentKey(BACK_REFERENCE, CONTAINER_ID, Long.class));
8788
}
8889

8990
@Test // DATAJDBC-251
@@ -93,10 +94,10 @@ public void generatedIdOfParentGetsPassedOnAsAdditionalParameter() {
9394

9495
interpreter.interpret(insert);
9596

96-
ArgumentCaptor<Map<String, Object>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
97+
ArgumentCaptor<ParentKeys> argumentCaptor = ArgumentCaptor.forClass(ParentKeys.class);
9798
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
9899

99-
assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID));
100+
assertThat(argumentCaptor.getValue().getParameters()).containsExactly(new ParentKey(BACK_REFERENCE, CONTAINER_ID, Long.class));
100101
}
101102

102103
static class Container {

0 commit comments

Comments
 (0)