Skip to content

Commit

Permalink
Partial implementation.
Browse files Browse the repository at this point in the history
Non trivial aggregates work with single value wrapped PK.
Simple aggregates work with composite id for insert, update, delete and exists.

See #574
  • Loading branch information
schauder committed Oct 2, 2024
1 parent 9fd001b commit afc72ec
Show file tree
Hide file tree
Showing 23 changed files with 981 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
*/
package org.springframework.data.jdbc.core.convert;

import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.relational.core.mapping.AggregatePath;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

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

RelationalPersistentProperty idProperty = path.getIdDefiningParentPath().getRequiredIdProperty();

if (value != null && idProperty.isEntity() && idProperty.isEmbedded()) {
// TODO: Fix for more than one property
RelationalPersistentEntity<?> propertyType = converter.getMappingContext().getRequiredPersistentEntity(idProperty.getType());
PersistentPropertyAccessor<Object> propertyAccessor = propertyType.getPropertyAccessor(value);
Object[] bucket = new Object[1];
propertyType.doWithProperties((SimplePropertyHandler) p -> {
if (bucket[0] != null) {
throw new IllegalStateException("Can't handle embededs with more than one property");
}
bucket[0] = propertyAccessor.getProperty(p);
});
value = bucket[0];
}

Identifier identifier = Identifier.of( //
path.getTableInfo().reverseColumnInfo().name(), //
value, //
converter.getColumnType(path.getIdDefiningParentPath().getRequiredIdProperty()) //
converter.getColumnType(idProperty) //
);

return new JdbcIdentifierBuilder(identifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.sql.SQLException;
import java.sql.SQLType;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
Expand Down Expand Up @@ -80,7 +81,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
* {@link #MappingJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory)}
* (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types.
*
* @param context must not be {@literal null}.
* @param context must not be {@literal null}.
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
*/
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {
Expand All @@ -98,12 +99,12 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r
/**
* Creates a new {@link MappingJdbcConverter} given {@link MappingContext}.
*
* @param context must not be {@literal null}.
* @param context must not be {@literal null}.
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
* @param typeFactory must not be {@literal null}
* @param typeFactory must not be {@literal null}
*/
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
CustomConversions conversions, JdbcTypeFactory typeFactory) {
CustomConversions conversions, JdbcTypeFactory typeFactory) {

super(context, conversions);

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

@Override
protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor documentAccessor,
ValueExpressionEvaluator evaluator, ConversionContext context) {
ValueExpressionEvaluator evaluator, ConversionContext context) {

if (context instanceof ResolvingConversionContext rcc) {

Expand Down Expand Up @@ -314,7 +315,7 @@ class ResolvingRelationalPropertyValueProvider implements RelationalPropertyValu
private final Identifier identifier;

private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider delegate, RowDocumentAccessor accessor,
ResolvingConversionContext context, Identifier identifier) {
ResolvingConversionContext context, Identifier identifier) {

AggregatePath path = context.aggregatePath();

Expand All @@ -323,15 +324,15 @@ private ResolvingRelationalPropertyValueProvider(AggregatePathValueProvider dele
this.context = context;
this.identifier = path.isEntity()
? potentiallyAppendIdentifier(identifier, path.getRequiredLeafEntity(),
property -> delegate.getValue(path.append(property)))
property -> delegate.getValue(path.append(property)))
: identifier;
}

/**
* Conditionally append the identifier if the entity has an identifier property.
*/
static Identifier potentiallyAppendIdentifier(Identifier base, RelationalPersistentEntity<?> entity,
Function<RelationalPersistentProperty, Object> getter) {
Function<RelationalPersistentProperty, Object> getter) {

if (entity.hasIdProperty()) {

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

RelationalPersistentProperty identifier = idDefiningParentPath.getRequiredIdProperty();
AggregatePath idPath = idDefiningParentPath.append(identifier);
Object value = delegate.getValue(idPath);
List<AggregatePath> idPaths = getMappingContext().getIdPaths(idDefiningParentPath.getRequiredLeafEntity());
RelationalPersistentProperty identifier = null;
Object value = null;
for (AggregatePath idPath : idPaths) {

// TODO this hack only works for single values.

identifier = idPath.getRequiredLeafProperty();
value = delegate.getValue(idDefiningParentPath.append(idPath));
}

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

Expand Down Expand Up @@ -460,7 +468,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) {

return context == this.context ? this
: new ResolvingRelationalPropertyValueProvider(delegate.withContext(context), accessor,
(ResolvingConversionContext) context, identifier);
(ResolvingConversionContext) context, identifier);
}
}

Expand All @@ -472,7 +480,7 @@ public RelationalPropertyValueProvider withContext(ConversionContext context) {
* @param identifier
*/
private record ResolvingConversionContext(ConversionContext delegate, AggregatePath aggregatePath,
Identifier identifier) implements ConversionContext {
Identifier identifier) implements ConversionContext {

@Override
public <S> S convert(Object source, TypeInformation<? extends S> typeHint) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.jdbc.core.convert;

import java.time.LocalTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand All @@ -37,6 +38,7 @@
import org.springframework.data.relational.core.sql.render.SqlRenderer;
import org.springframework.data.util.Lazy;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

Expand Down Expand Up @@ -451,7 +453,7 @@ String createDeleteAllSql(@Nullable PersistentPropertyPath<RelationalPersistentP
*/
String createDeleteByPath(PersistentPropertyPath<RelationalPersistentProperty> path) {
return createDeleteByPathAndCriteria(mappingContext.getAggregatePath(path),
filterColumn -> filterColumn.isEqualTo(getBindMarker(ROOT_ID_PARAMETER)));
filterColumn -> filterColumn.isEqualTo(getBindMarker(entity.getIdColumn())));
}

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

private String createFindOneSql() {

Select select = selectBuilder().where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER))) //
.build();
SelectBuilder.SelectWhereAndOr select = null;

return render(select);
return render(selectBuilder().where(singleIdWhereCondition()).build());
}

private Condition singleIdWhereCondition() {

Condition aggregate = null;
for (Column column : getIdColumns()) {
Comparison condition = column.isEqualTo(getBindMarker(column.getName()));

if (aggregate == null) {
aggregate = condition;
} else {
aggregate = aggregate.and(condition);
}
}

Assert.state(aggregate != null, "We need at least one id column");

return aggregate;
}

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

private String createFindAllInListSql() {

Select select = selectBuilder().where(getIdColumn().in(getBindMarker(IDS_SQL_PARAMETER))).build();
In condition = multiIdWhereClause();
Select select = selectBuilder().where(condition).build();

return render(select);
}

private String createExistsSql() {
private In multiIdWhereClause() {

List<Column> idColumns = getIdColumns();
TupleExpression tuple = TupleExpression.create(idColumns);
return Conditions.in(tuple, getBindMarker(IDS_SQL_PARAMETER));
}

private String createExistsSql() {
Table table = getTable();

Select select = StatementBuilder //
.select(Functions.count(getIdColumn())) //
.from(table) //
.where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER))) //
.where(singleIdWhereCondition()) //
.build();

return render(select);
Expand Down Expand Up @@ -715,7 +741,7 @@ private UpdateBuilder.UpdateWhereAndOr createBaseUpdate() {
return Update.builder() //
.table(table) //
.set(assignments) //
.where(getIdColumn().isEqualTo(getBindMarker(entity.getIdColumn())));
.where(singleIdWhereCondition());
}

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

return Delete.builder().from(table) //
.where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER)));
.where(singleIdWhereCondition());
}

private DeleteBuilder.DeleteWhereAndOr createBaseDeleteByIdIn(Table table) {

return Delete.builder().from(table) //
.where(getIdColumn().in(getBindMarker(IDS_SQL_PARAMETER)));
.where(multiIdWhereClause());
}

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

private Column getIdColumn() {
return sqlContext.getIdColumn();

List<AggregatePath> idPaths = mappingContext.getIdPaths(entity);

AggregatePath idAggregatePath = idPaths.get(0);// TODO: hack for single column

return sqlContext.getColumn(idAggregatePath);
}

private List<Column> getIdColumns() {

List<AggregatePath> idPaths = mappingContext.getIdPaths(entity);

List<Column> result = new ArrayList<>(idPaths.size());

for (AggregatePath idPath : idPaths) {
result.add(sqlContext.getColumn(idPath));
}
return result;
}

private Column getVersionColumn() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
import org.springframework.data.relational.core.conversion.IdValueSource;
import org.springframework.data.relational.core.mapping.AggregatePath;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
Expand Down Expand Up @@ -78,9 +80,13 @@ <T> SqlIdentifierParameterSource forInsert(T instance, Class<T> domainType, Iden

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

RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
Object idValue = persistentEntity.getIdentifierAccessor(instance).getRequiredIdentifier();
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
PersistentPropertyPathAccessor<T> propertyPathAccessor = persistentEntity.getPropertyPathAccessor(instance);
for (AggregatePath idPath : context.getIdPaths(persistentEntity)) {

Object idValue = propertyPathAccessor.getProperty(idPath.getRequiredPersistentPropertyPath());
RelationalPersistentProperty idProperty = idPath.getRequiredLeafProperty();
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
}
}
return parameterSource;
}
Expand Down Expand Up @@ -112,12 +118,38 @@ <T> SqlIdentifierParameterSource forQueryById(Object id, Class<T> domainType, Sq

SqlIdentifierParameterSource parameterSource = new SqlIdentifierParameterSource();

addConvertedPropertyValue( //
parameterSource, //
getRequiredPersistentEntity(domainType).getRequiredIdProperty(), //
id, //
name //
);
RelationalPersistentEntity<T> entity = getRequiredPersistentEntity(domainType);

RelationalPersistentProperty singleIdProperty = entity.getRequiredIdProperty();

if (singleIdProperty.isEntity()) {

RelationalPersistentEntity<?> complexId = context.getPersistentEntity(singleIdProperty);
PersistentPropertyPathAccessor<Object> accessor = complexId.getPropertyPathAccessor(id);

List<AggregatePath> idPaths = context.getIdPaths(entity);

for (AggregatePath idPath : idPaths) {
AggregatePath idElementPath = idPath.getTail();
Object idValue = accessor.getProperty(idElementPath.getRequiredPersistentPropertyPath());

addConvertedPropertyValue( //
parameterSource, //
idElementPath.getRequiredLeafProperty(), //
idValue, //
idElementPath.getColumnInfo().name() //
);
}

} else {

addConvertedPropertyValue( //
parameterSource, //
singleIdProperty, //
id, //
singleIdProperty.getColumnName() //
);
}
return parameterSource;
}

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

SqlIdentifierParameterSource parameterSource = new SqlIdentifierParameterSource();

addConvertedPropertyValuesAsList(parameterSource, getRequiredPersistentEntity(domainType).getRequiredIdProperty(),
ids);
RelationalPersistentEntity<?> entity = context.getPersistentEntity(domainType);
RelationalPersistentProperty singleIdProperty = entity.getRequiredIdProperty();

if (singleIdProperty.isEntity()) {

RelationalPersistentEntity<?> complexId = context.getPersistentEntity(singleIdProperty);
List<AggregatePath> idPaths = context.getIdPaths(entity);

List<Object[]> parameterValues = new ArrayList<>();

for (Object id : ids) {

PersistentPropertyPathAccessor<Object> accessor = complexId.getPropertyPathAccessor(id);

Object[] tuple = new Object[idPaths.size()];
int index = 0;
for (AggregatePath idPath : idPaths) {
AggregatePath idElementPath = idPath.getTail();
tuple[index] = accessor.getProperty(idElementPath.getRequiredPersistentPropertyPath());
index++;
}
parameterValues.add(tuple);
}

parameterSource.addValue(SqlGenerator.IDS_SQL_PARAMETER, parameterValues);
} else {
addConvertedPropertyValuesAsList(parameterSource, getRequiredPersistentEntity(domainType).getRequiredIdProperty(),
ids);
}
return parameterSource;
}

Expand Down
Loading

0 comments on commit afc72ec

Please sign in to comment.