Skip to content

Commit afc72ec

Browse files
committed
Partial implementation.
Non trivial aggregates work with single value wrapped PK. Simple aggregates work with composite id for insert, update, delete and exists. See #574
1 parent 9fd001b commit afc72ec

23 files changed

+981
-113
lines changed

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

+22-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
*/
1616
package org.springframework.data.jdbc.core.convert;
1717

18+
import org.springframework.beans.factory.config.ConstructorArgumentValues;
19+
import org.springframework.data.mapping.PersistentPropertyAccessor;
20+
import org.springframework.data.mapping.SimplePropertyHandler;
1821
import org.springframework.data.relational.core.mapping.AggregatePath;
22+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
23+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
1924
import org.springframework.lang.Nullable;
2025
import org.springframework.util.Assert;
2126

@@ -42,10 +47,26 @@ public static JdbcIdentifierBuilder empty() {
4247
*/
4348
public static JdbcIdentifierBuilder forBackReferences(JdbcConverter converter, AggregatePath path, Object value) {
4449

50+
RelationalPersistentProperty idProperty = path.getIdDefiningParentPath().getRequiredIdProperty();
51+
52+
if (value != null && idProperty.isEntity() && idProperty.isEmbedded()) {
53+
// TODO: Fix for more than one property
54+
RelationalPersistentEntity<?> propertyType = converter.getMappingContext().getRequiredPersistentEntity(idProperty.getType());
55+
PersistentPropertyAccessor<Object> propertyAccessor = propertyType.getPropertyAccessor(value);
56+
Object[] bucket = new Object[1];
57+
propertyType.doWithProperties((SimplePropertyHandler) p -> {
58+
if (bucket[0] != null) {
59+
throw new IllegalStateException("Can't handle embededs with more than one property");
60+
}
61+
bucket[0] = propertyAccessor.getProperty(p);
62+
});
63+
value = bucket[0];
64+
}
65+
4566
Identifier identifier = Identifier.of( //
4667
path.getTableInfo().reverseColumnInfo().name(), //
4768
value, //
48-
converter.getColumnType(path.getIdDefiningParentPath().getRequiredIdProperty()) //
69+
converter.getColumnType(idProperty) //
4970
);
5071

5172
return new JdbcIdentifierBuilder(identifier);

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

+21-13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.sql.SQLException;
2121
import java.sql.SQLType;
2222
import java.util.Iterator;
23+
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Optional;
2526
import java.util.function.Function;
@@ -80,7 +81,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
8081
* {@link #MappingJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory)}
8182
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
8283
*
83-
* @param context must not be {@literal null}.
84+
* @param context must not be {@literal null}.
8485
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
8586
*/
8687
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {
@@ -98,12 +99,12 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r
9899
/**
99100
* Creates a new {@link MappingJdbcConverter} given {@link MappingContext}.
100101
*
101-
* @param context must not be {@literal null}.
102+
* @param context must not be {@literal null}.
102103
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
103-
* @param typeFactory must not be {@literal null}
104+
* @param typeFactory must not be {@literal null}
104105
*/
105106
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
106-
CustomConversions conversions, JdbcTypeFactory typeFactory) {
107+
CustomConversions conversions, JdbcTypeFactory typeFactory) {
107108

108109
super(context, conversions);
109110

@@ -285,7 +286,7 @@ public <R> R readAndResolve(TypeInformation<R> type, RowDocument source, Identif
285286

286287
@Override
287288
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
288-
ValueExpressionEvaluator evaluator, ConversionContext context) {
289+
ValueExpressionEvaluator evaluator, ConversionContext context) {
289290

290291
if (context instanceof ResolvingConversionContext rcc) {
291292

@@ -314,7 +315,7 @@ class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValu
314315
private final Identifier identifier;
315316

316317
private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor,
317-
ResolvingConversionContext context, Identifier identifier) {
318+
ResolvingConversionContext context, Identifier identifier) {
318319

319320
AggregatePath path = context.aggregatePath();
320321

@@ -323,15 +324,15 @@ private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider dele
323324
this.context = context;
324325
this.identifier = path.isEntity()
325326
? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(),
326-
property -> delegate.getValue(path.append(property)))
327+
property -> delegate.getValue(path.append(property)))
327328
: identifier;
328329
}
329330

330331
/**
331332
* Conditionally append the identifier if the entity has an identifier property.
332333
*/
333334
static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity<?> entity,
334-
Function<RelationalPersistentProperty, Object> getter) {
335+
Function<RelationalPersistentProperty, Object> getter) {
335336

336337
if (entity.hasIdProperty()) {
337338

@@ -368,9 +369,16 @@ public <T> T getPropertyValue(RelationalPersistentProperty property) {
368369
// references and possibly keys, that form an id
369370
if (idDefiningParentPath.hasIdProperty()) {
370371

371-
RelationalPersistentProperty identifier = idDefiningParentPath.getRequiredIdProperty();
372-
AggregatePath idPath = idDefiningParentPath.append(identifier);
373-
Object value = delegate.getValue(idPath);
372+
List<AggregatePath> idPaths = getMappingContext().getIdPaths(idDefiningParentPath.getRequiredLeafEntity());
373+
RelationalPersistentProperty identifier = null;
374+
Object value = null;
375+
for (AggregatePath idPath : idPaths) {
376+
377+
// TODO this hack only works for single values.
378+
379+
identifier = idPath.getRequiredLeafProperty();
380+
value = delegate.getValue(idDefiningParentPath.append(idPath));
381+
}
374382

375383
Assert.state(value != null, "Identifier value must not be null at this point");
376384

@@ -460,7 +468,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) {
460468

461469
return context == this.context ? this
462470
: new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor,
463-
(ResolvingConversionContext) context, identifier);
471+
(ResolvingConversionContext) context, identifier);
464472
}
465473
}
466474

@@ -472,7 +480,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) {
472480
* @param identifier
473481
*/
474482
private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath,
475-
Identifier identifier) implements ConversionContext {
483+
Identifier identifier) implements ConversionContext {
476484

477485
@Override
478486
public <S> S convert(Object source, TypeInformation<? extends S> typeHint) {

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

+54-11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.jdbc.core.convert;
1717

18+
import java.time.LocalTime;
1819
import java.util.*;
1920
import java.util.function.Function;
2021
import java.util.stream.Collectors;
@@ -37,6 +38,7 @@
3738
import org.springframework.data.relational.core.sql.render.SqlRenderer;
3839
import org.springframework.data.util.Lazy;
3940
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
41+
import org.springframework.lang.NonNull;
4042
import org.springframework.lang.Nullable;
4143
import org.springframework.util.Assert;
4244

@@ -451,7 +453,7 @@ String createDeleteAllSql(@Nullable PersistentPropertyPath<RelationalPersistentP
451453
*/
452454
String createDeleteByPath(PersistentPropertyPath<RelationalPersistentProperty> path) {
453455
return createDeleteByPathAndCriteria(mappingContext.getAggregatePath(path),
454-
filterColumn -> filterColumn.isEqualTo(getBindMarker(ROOT_ID_PARAMETER)));
456+
filterColumn -> filterColumn.isEqualTo(getBindMarker(entity.getIdColumn())));
455457
}
456458

457459
/**
@@ -469,10 +471,27 @@ String createDeleteInByPath(PersistentPropertyPath<RelationalPersistentProperty>
469471

470472
private String createFindOneSql() {
471473

472-
Select select = selectBuilder().where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER))) //
473-
.build();
474+
SelectBuilder.SelectWhereAndOr select = null;
474475

475-
return render(select);
476+
return render(selectBuilder().where(singleIdWhereCondition()).build());
477+
}
478+
479+
private Condition singleIdWhereCondition() {
480+
481+
Condition aggregate = null;
482+
for (Column column : getIdColumns()) {
483+
Comparison condition = column.isEqualTo(getBindMarker(column.getName()));
484+
485+
if (aggregate == null) {
486+
aggregate = condition;
487+
} else {
488+
aggregate = aggregate.and(condition);
489+
}
490+
}
491+
492+
Assert.state(aggregate != null, "We need at least one id column");
493+
494+
return aggregate;
476495
}
477496

478497
private String createAcquireLockById(LockMode lockMode) {
@@ -632,19 +651,26 @@ Join getJoin(AggregatePath path) {
632651

633652
private String createFindAllInListSql() {
634653

635-
Select select = selectBuilder().where(getIdColumn().in(getBindMarker(IDS_SQL_PARAMETER))).build();
654+
In condition = multiIdWhereClause();
655+
Select select = selectBuilder().where(condition).build();
636656

637657
return render(select);
638658
}
639659

640-
private String createExistsSql() {
660+
private In multiIdWhereClause() {
641661

662+
List<Column> idColumns = getIdColumns();
663+
TupleExpression tuple = TupleExpression.create(idColumns);
664+
return Conditions.in(tuple, getBindMarker(IDS_SQL_PARAMETER));
665+
}
666+
667+
private String createExistsSql() {
642668
Table table = getTable();
643669

644670
Select select = StatementBuilder //
645671
.select(Functions.count(getIdColumn())) //
646672
.from(table) //
647-
.where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER))) //
673+
.where(singleIdWhereCondition()) //
648674
.build();
649675

650676
return render(select);
@@ -715,7 +741,7 @@ private UpdateBuilder.UpdateWhereAndOr createBaseUpdate() {
715741
return Update.builder() //
716742
.table(table) //
717743
.set(assignments) //
718-
.where(getIdColumn().isEqualTo(getBindMarker(entity.getIdColumn())));
744+
.where(singleIdWhereCondition());
719745
}
720746

721747
private String createDeleteByIdSql() {
@@ -738,13 +764,13 @@ private String createDeleteByIdAndVersionSql() {
738764
private DeleteBuilder.DeleteWhereAndOr createBaseDeleteById(Table table) {
739765

740766
return Delete.builder().from(table) //
741-
.where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER)));
767+
.where(singleIdWhereCondition());
742768
}
743769

744770
private DeleteBuilder.DeleteWhereAndOr createBaseDeleteByIdIn(Table table) {
745771

746772
return Delete.builder().from(table) //
747-
.where(getIdColumn().in(getBindMarker(IDS_SQL_PARAMETER)));
773+
.where(multiIdWhereClause());
748774
}
749775

750776
private String createDeleteByPathAndCriteria(AggregatePath path, Function<Column, Condition> rootCondition) {
@@ -804,7 +830,24 @@ private Table getTable() {
804830
}
805831

806832
private Column getIdColumn() {
807-
return sqlContext.getIdColumn();
833+
834+
List<AggregatePath> idPaths = mappingContext.getIdPaths(entity);
835+
836+
AggregatePath idAggregatePath = idPaths.get(0);// TODO: hack for single column
837+
838+
return sqlContext.getColumn(idAggregatePath);
839+
}
840+
841+
private List<Column> getIdColumns() {
842+
843+
List<AggregatePath> idPaths = mappingContext.getIdPaths(entity);
844+
845+
List<Column> result = new ArrayList<>(idPaths.size());
846+
847+
for (AggregatePath idPath : idPaths) {
848+
result.add(sqlContext.getColumn(idPath));
849+
}
850+
return result;
808851
}
809852

810853
private Column getVersionColumn() {

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

+69-11
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import org.springframework.data.jdbc.support.JdbcUtil;
2626
import org.springframework.data.mapping.PersistentProperty;
2727
import org.springframework.data.mapping.PersistentPropertyAccessor;
28+
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
2829
import org.springframework.data.relational.core.conversion.IdValueSource;
30+
import org.springframework.data.relational.core.mapping.AggregatePath;
2931
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3032
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3133
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -78,9 +80,13 @@ <T> SqlIdentifierParameterSource forInsert(T instance, Class<T> domainType, Iden
7880

7981
if (IdValueSource.PROVIDED.equals(idValueSource)) {
8082

81-
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
82-
Object idValue = persistentEntity.getIdentifierAccessor(instance).getRequiredIdentifier();
83-
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
83+
PersistentPropertyPathAccessor<T> propertyPathAccessor = persistentEntity.getPropertyPathAccessor(instance);
84+
for (AggregatePath idPath : context.getIdPaths(persistentEntity)) {
85+
86+
Object idValue = propertyPathAccessor.getProperty(idPath.getRequiredPersistentPropertyPath());
87+
RelationalPersistentProperty idProperty = idPath.getRequiredLeafProperty();
88+
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
89+
}
8490
}
8591
return parameterSource;
8692
}
@@ -112,12 +118,38 @@ <T> SqlIdentifierParameterSource forQueryById(Object id, Class<T> domainType, Sq
112118

113119
SqlIdentifierParameterSource parameterSource = new SqlIdentifierParameterSource();
114120

115-
addConvertedPropertyValue( //
116-
parameterSource, //
117-
getRequiredPersistentEntity(domainType).getRequiredIdProperty(), //
118-
id, //
119-
name //
120-
);
121+
RelationalPersistentEntity<T> entity = getRequiredPersistentEntity(domainType);
122+
123+
RelationalPersistentProperty singleIdProperty = entity.getRequiredIdProperty();
124+
125+
if (singleIdProperty.isEntity()) {
126+
127+
RelationalPersistentEntity<?> complexId = context.getPersistentEntity(singleIdProperty);
128+
PersistentPropertyPathAccessor<Object> accessor = complexId.getPropertyPathAccessor(id);
129+
130+
List<AggregatePath> idPaths = context.getIdPaths(entity);
131+
132+
for (AggregatePath idPath : idPaths) {
133+
AggregatePath idElementPath = idPath.getTail();
134+
Object idValue = accessor.getProperty(idElementPath.getRequiredPersistentPropertyPath());
135+
136+
addConvertedPropertyValue( //
137+
parameterSource, //
138+
idElementPath.getRequiredLeafProperty(), //
139+
idValue, //
140+
idElementPath.getColumnInfo().name() //
141+
);
142+
}
143+
144+
} else {
145+
146+
addConvertedPropertyValue( //
147+
parameterSource, //
148+
singleIdProperty, //
149+
id, //
150+
singleIdProperty.getColumnName() //
151+
);
152+
}
121153
return parameterSource;
122154
}
123155

@@ -133,9 +165,35 @@ <T> SqlIdentifierParameterSource forQueryByIds(Iterable<?> ids, Class<T> domainT
133165

134166
SqlIdentifierParameterSource parameterSource = new SqlIdentifierParameterSource();
135167

136-
addConvertedPropertyValuesAsList(parameterSource, getRequiredPersistentEntity(domainType).getRequiredIdProperty(),
137-
ids);
168+
RelationalPersistentEntity<?> entity = context.getPersistentEntity(domainType);
169+
RelationalPersistentProperty singleIdProperty = entity.getRequiredIdProperty();
170+
171+
if (singleIdProperty.isEntity()) {
172+
173+
RelationalPersistentEntity<?> complexId = context.getPersistentEntity(singleIdProperty);
174+
List<AggregatePath> idPaths = context.getIdPaths(entity);
175+
176+
List<Object[]> parameterValues = new ArrayList<>();
138177

178+
for (Object id : ids) {
179+
180+
PersistentPropertyPathAccessor<Object> accessor = complexId.getPropertyPathAccessor(id);
181+
182+
Object[] tuple = new Object[idPaths.size()];
183+
int index = 0;
184+
for (AggregatePath idPath : idPaths) {
185+
AggregatePath idElementPath = idPath.getTail();
186+
tuple[index] = accessor.getProperty(idElementPath.getRequiredPersistentPropertyPath());
187+
index++;
188+
}
189+
parameterValues.add(tuple);
190+
}
191+
192+
parameterSource.addValue(SqlGenerator.IDS_SQL_PARAMETER, parameterValues);
193+
} else {
194+
addConvertedPropertyValuesAsList(parameterSource, getRequiredPersistentEntity(domainType).getRequiredIdProperty(),
195+
ids);
196+
}
139197
return parameterSource;
140198
}
141199

0 commit comments

Comments
 (0)