diff --git a/pom.xml b/pom.xml index 3097538048..85d484dab4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 3.2.0-SNAPSHOT + 3.2.0-refactor-path-extension-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index d834798834..bd788255d9 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.2.0-SNAPSHOT + 3.2.0-refactor-path-extension-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 32f9269501..3fcb709470 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 3.2.0-SNAPSHOT + 3.2.0-refactor-path-extension-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.2.0-SNAPSHOT + 3.2.0-refactor-path-extension-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java index 254fc30766..3131273806 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java @@ -15,16 +15,7 @@ */ package org.springframework.data.jdbc.core; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -38,11 +29,13 @@ import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PersistentPropertyPathAccessor; -import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.DbActionExecutionResult; import org.springframework.data.relational.core.conversion.IdValueSource; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.LockMode; @@ -65,7 +58,7 @@ class JdbcAggregateChangeExecutionContext { private static final String UPDATE_FAILED = "Failed to update entity [%s]; Id [%s] not found in database"; private static final String UPDATE_FAILED_OPTIMISTIC_LOCKING = "Failed to update entity [%s]; The entity was updated since it was rea or it isn't in the database at all"; - private final MappingContext, ? extends RelationalPersistentProperty> context; + private final RelationalMappingContext context; private final JdbcConverter converter; private final DataAccessStrategy accessStrategy; @@ -184,12 +177,11 @@ private Identifier getParentKeys(DbAction.WithDependingOn action, JdbcConvert Object id = getParentId(action); JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder // - .forBackReferences(converter, new PersistentPropertyPathExtension(context, action.getPropertyPath()), id); + .forBackReferences(converter, context.getAggregatePath(action.getPropertyPath()), id); for (Map.Entry, Object> qualifier : action.getQualifiers() .entrySet()) { - identifier = identifier.withQualifier(new PersistentPropertyPathExtension(context, qualifier.getKey()), - qualifier.getValue()); + identifier = identifier.withQualifier(context.getAggregatePath(qualifier.getKey()), qualifier.getValue()); } return identifier.build(); @@ -197,26 +189,22 @@ private Identifier getParentKeys(DbAction.WithDependingOn action, JdbcConvert private Object getParentId(DbAction.WithDependingOn action) { - PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, action.getPropertyPath()); - PersistentPropertyPathExtension idPath = path.getIdDefiningParentPath(); - - DbAction.WithEntity idOwningAction = getIdOwningAction(action, idPath); + DbAction.WithEntity idOwningAction = getIdOwningAction(action, AggregatePathUtil.getIdDefiningParentPath(context.getAggregatePath(action.getPropertyPath()))); return getPotentialGeneratedIdFrom(idOwningAction); } - private DbAction.WithEntity getIdOwningAction(DbAction.WithEntity action, - PersistentPropertyPathExtension idPath) { + private DbAction.WithEntity getIdOwningAction(DbAction.WithEntity action, AggregatePath idPath) { if (!(action instanceof DbAction.WithDependingOn withDependingOn)) { - Assert.state(idPath.getLength() == 0, + Assert.state(idPath.isRoot(), "When the id path is not empty the id providing action should be of type WithDependingOn"); return action; } - if (idPath.matches(withDependingOn.getPropertyPath())) { + if (idPath.equals(context.getAggregatePath(withDependingOn.getPropertyPath()))) { return action; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index 8f2268752f..3151e4a4f7 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -25,7 +25,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.ConverterNotFoundException; @@ -47,9 +46,12 @@ import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider; import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.RelationalConverter; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.ForeignTableDetector; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.core.mapping.SingleColumnAggregatePath; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; @@ -86,14 +88,14 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc * Creates a new {@link BasicRelationalConverter} given {@link MappingContext} and a * {@link JdbcTypeFactory#unsupported() no-op type factory} throwing {@link UnsupportedOperationException} on type * creation. Use - * {@link #BasicJdbcConverter(MappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)} + * {@link #BasicJdbcConverter(RelationalMappingContext, RelationResolver, CustomConversions, JdbcTypeFactory, IdentifierProcessing)} * (MappingContext, RelationResolver, JdbcTypeFactory)} to convert arrays and large objects into JDBC-specific types. * * @param context must not be {@literal null}. * @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}. */ public BasicJdbcConverter( - MappingContext, ? extends RelationalPersistentProperty> context, + RelationalMappingContext context, RelationResolver relationResolver) { super(context, new JdbcCustomConversions()); @@ -116,7 +118,7 @@ public BasicJdbcConverter( * @since 2.0 */ public BasicJdbcConverter( - MappingContext, ? extends RelationalPersistentProperty> context, + RelationalMappingContext context, RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) { @@ -300,12 +302,13 @@ private JdbcValue tryToConvertToJdbcValue(@Nullable Object value) { @Override public T mapRow(RelationalPersistentEntity entity, ResultSet resultSet, Object key) { - return new ReadingContext(new PersistentPropertyPathExtension(getMappingContext(), entity), + return new ReadingContext(getMappingContext().getAggregatePath( entity), new ResultSetAccessor(resultSet), Identifier.empty(), key).mapRow(); } + @Override - public T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key) { + public T mapRow(AggregatePath path, ResultSet resultSet, Identifier identifier, Object key) { return new ReadingContext(path, new ResultSetAccessor(resultSet), identifier, key).mapRow(); } @@ -350,8 +353,8 @@ private class ReadingContext { private final RelationalPersistentEntity entity; - private final PersistentPropertyPathExtension rootPath; - private final PersistentPropertyPathExtension path; + private final AggregatePath rootPath; + private final AggregatePath path; private final Identifier identifier; private final Object key; @@ -360,7 +363,7 @@ private class ReadingContext { private final ResultSetAccessor accessor; @SuppressWarnings("unchecked") - private ReadingContext(PersistentPropertyPathExtension rootPath, ResultSetAccessor accessor, Identifier identifier, + private ReadingContext(AggregatePath rootPath, ResultSetAccessor accessor, Identifier identifier, Object key) { RelationalPersistentEntity entity = (RelationalPersistentEntity) rootPath.getLeafEntity(); @@ -368,7 +371,7 @@ private ReadingContext(PersistentPropertyPathExtension rootPath, ResultSetAccess this.entity = entity; this.rootPath = rootPath; - this.path = new PersistentPropertyPathExtension(getMappingContext(), this.entity); + this.path = getMappingContext().getAggregatePath( this.entity); this.identifier = identifier; this.key = key; this.propertyValueProvider = new JdbcPropertyValueProvider(path, accessor); @@ -376,10 +379,11 @@ private ReadingContext(PersistentPropertyPathExtension rootPath, ResultSetAccess this.accessor = accessor; } - private ReadingContext(RelationalPersistentEntity entity, PersistentPropertyPathExtension rootPath, - PersistentPropertyPathExtension path, Identifier identifier, Object key, + private ReadingContext(RelationalPersistentEntity entity, AggregatePath rootPath, + AggregatePath path, Identifier identifier, Object key, JdbcPropertyValueProvider propertyValueProvider, JdbcBackReferencePropertyValueProvider backReferencePropertyValueProvider, ResultSetAccessor accessor) { + this.entity = entity; this.rootPath = rootPath; this.path = path; @@ -393,7 +397,7 @@ private ReadingContext(RelationalPersistentEntity entity, PersistentPropertyP private ReadingContext extendBy(RelationalPersistentProperty property) { return new ReadingContext<>( (RelationalPersistentEntity) getMappingContext().getRequiredPersistentEntity(property.getActualType()), - rootPath.extendBy(property), path.extendBy(property), identifier, key, + rootPath.append(property), path.append(property), identifier, key, propertyValueProvider.extendBy(property), backReferencePropertyValueProvider.extendBy(property), accessor); } @@ -453,10 +457,10 @@ private Object readOrLoadProperty(@Nullable Object id, RelationalPersistentPrope private Iterable resolveRelation(@Nullable Object id, RelationalPersistentProperty property) { Identifier identifier = id == null // - ? this.identifier.withPart(rootPath.getQualifierColumn(), key, Object.class) // - : Identifier.of(rootPath.extendBy(property).getReverseColumnName(), id, Object.class); + ? this.identifier.withPart(ForeignTableDetector.of(rootPath).getQualifierColumn(), key, Object.class) // + : Identifier.of(SingleColumnAggregatePath.of(rootPath.append(property)).getReverseColumnName(), id, Object.class); - PersistentPropertyPath propertyPath = path.extendBy(property) + PersistentPropertyPath propertyPath = path.append(property) .getRequiredPersistentPropertyPath(); return relationResolver.findAllByPath(identifier, propertyPath); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java index 95bc5138ca..0ac109576a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java @@ -28,12 +28,12 @@ import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.conversion.IdValueSource; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.ForeignTableDetector; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.query.Query; -import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.LockMode; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.jdbc.core.RowMapper; @@ -297,8 +297,8 @@ public Iterable findAllByPath(Identifier identifier, Assert.notNull(identifier, "identifier must not be null"); Assert.notNull(propertyPath, "propertyPath must not be null"); - PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, propertyPath); - Class actualType = path.getActualType(); + AggregatePath path = context.getAggregatePath(propertyPath); + Class actualType = path.getLeafEntity().getType(); String findAllByProperty = sql(actualType) // .getFindAllByProperty(identifier, propertyPath); @@ -339,8 +339,7 @@ public Optional findOne(Query query, Class domainType) { String sqlQuery = sql(domainType).selectByQuery(query, parameterSource); try { - return Optional.ofNullable( - operations.queryForObject(sqlQuery, parameterSource, getEntityRowMapper(domainType))); + return Optional.ofNullable(operations.queryForObject(sqlQuery, parameterSource, getEntityRowMapper(domainType))); } catch (EmptyResultDataAccessException e) { return Optional.empty(); } @@ -394,14 +393,13 @@ private EntityRowMapper getEntityRowMapper(Class domainType) { return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), converter); } - private EntityRowMapper getEntityRowMapper(PersistentPropertyPathExtension path, Identifier identifier) { + private EntityRowMapper getEntityRowMapper(AggregatePath path, Identifier identifier) { return new EntityRowMapper<>(path, converter, identifier); } - private RowMapper getMapEntityRowMapper(PersistentPropertyPathExtension path, Identifier identifier) { + private RowMapper getMapEntityRowMapper(AggregatePath path, Identifier identifier) { - SqlIdentifier keyColumn = path.getQualifierColumn(); - Assert.notNull(keyColumn, () -> "KeyColumn must not be null for " + path); + SqlIdentifier keyColumn = ForeignTableDetector.of(path).getQualifierColumn(); return new MapEntityRowMapper<>(path, converter, identifier, keyColumn); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java index 5014398fab..fe6d0b7a38 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/EntityRowMapper.java @@ -17,6 +17,7 @@ import java.sql.ResultSet; +import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.jdbc.core.RowMapper; @@ -35,13 +36,28 @@ public class EntityRowMapper implements RowMapper { private final RelationalPersistentEntity entity; - private final PersistentPropertyPathExtension path; + private final AggregatePath path; private final JdbcConverter converter; private final Identifier identifier; + /** + * + * + * @deprecated use {@link EntityRowMapper#EntityRowMapper(AggregatePath, JdbcConverter, Identifier)} instead + */ + @Deprecated(since = "3.2", forRemoval = true) @SuppressWarnings("unchecked") public EntityRowMapper(PersistentPropertyPathExtension path, JdbcConverter converter, Identifier identifier) { + this.entity = (RelationalPersistentEntity) path.getLeafEntity(); + this.path = path.getAggregatePath(); + this.converter = converter; + this.identifier = identifier; + } + + @SuppressWarnings("unchecked") + public EntityRowMapper(AggregatePath path, JdbcConverter converter, Identifier identifier) { + this.entity = (RelationalPersistentEntity) path.getLeafEntity(); this.path = path; this.converter = converter; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcBackReferencePropertyValueProvider.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcBackReferencePropertyValueProvider.java index 65cdfc0ec7..ac34481d4a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcBackReferencePropertyValueProvider.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcBackReferencePropertyValueProvider.java @@ -16,9 +16,9 @@ package org.springframework.data.jdbc.core.convert; import org.springframework.data.mapping.model.PropertyValueProvider; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.data.relational.core.sql.IdentifierProcessing; /** * {@link PropertyValueProvider} obtaining values from a {@link ResultSetAccessor}. For a given id property it provides @@ -31,14 +31,14 @@ */ class JdbcBackReferencePropertyValueProvider implements PropertyValueProvider { - private final PersistentPropertyPathExtension basePath; + private final AggregatePath basePath; private final ResultSetAccessor resultSet; /** * @param basePath path from the aggregate root relative to which all properties get resolved. * @param resultSet the {@link ResultSetAccessor} from which to obtain the actual values. */ - JdbcBackReferencePropertyValueProvider(PersistentPropertyPathExtension basePath, ResultSetAccessor resultSet) { + JdbcBackReferencePropertyValueProvider(AggregatePath basePath, ResultSetAccessor resultSet) { this.resultSet = resultSet; this.basePath = basePath; @@ -46,10 +46,10 @@ class JdbcBackReferencePropertyValueProvider implements PropertyValueProvider T getPropertyValue(RelationalPersistentProperty property) { - return (T) resultSet.getObject(basePath.extendBy(property).getReverseColumnNameAlias().getReference()); + return (T) resultSet.getObject(AggregatePathUtil.getReverseColumnNameAlias(basePath.append(property)).getReference()); } public JdbcBackReferencePropertyValueProvider extendBy(RelationalPersistentProperty property) { - return new JdbcBackReferencePropertyValueProvider(basePath.extendBy(property), resultSet); + return new JdbcBackReferencePropertyValueProvider(basePath.append(property), resultSet); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java index 957afd66c1..3de2ceb2ec 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java @@ -19,8 +19,11 @@ import java.sql.SQLType; import org.springframework.data.jdbc.core.mapping.JdbcValue; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.conversion.RelationalConverter; +import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.util.TypeInformation; @@ -67,8 +70,24 @@ public interface JdbcConverter extends RelationalConverter { * @param key primary key. * @param * @return + * @deprecated use {@link #mapRow(AggregatePath, ResultSet, Identifier, Object)} instead. */ - T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key); + @Deprecated(since = "3.2", forRemoval = true) + default T mapRow(PersistentPropertyPathExtension path, ResultSet resultSet, Identifier identifier, Object key){ + return mapRow(path.getAggregatePath(), resultSet, identifier, key); + }; + + /** + * Read the current row from {@link ResultSet} to an {@link AggregatePath#getLeafEntity()} entity}. + * + * @param path path to the owning property. + * @param resultSet the {@link ResultSet} to read from. + * @param identifier entity identifier. + * @param key primary key. + * @param + * @return + */ + T mapRow(AggregatePath path, ResultSet resultSet, Identifier identifier, Object key); /** * The type to be used to store this property in the database. Multidimensional arrays are unwrapped to reflect a @@ -88,4 +107,7 @@ public interface JdbcConverter extends RelationalConverter { * @since 2.0 */ SQLType getTargetSqlType(RelationalPersistentProperty property); + + @Override + RelationalMappingContext getMappingContext(); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java index 052428b596..0b64785207 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java @@ -15,6 +15,9 @@ */ package org.springframework.data.jdbc.core.convert; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; +import org.springframework.data.relational.core.mapping.ForeignTableDetector; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -39,14 +42,25 @@ public static JdbcIdentifierBuilder empty() { /** * Creates ParentKeys with backreference for the given path and value of the parents id. + * + * @deprecated Use {@link #forBackReferences(JdbcConverter, AggregatePath, Object)} instead. */ + @Deprecated(since = "3.2", forRemoval = true) public static JdbcIdentifierBuilder forBackReferences(JdbcConverter converter, PersistentPropertyPathExtension path, @Nullable Object value) { + return forBackReferences(converter, path.getAggregatePath(), value); + } + + /** + * Creates ParentKeys with backreference for the given path and value of the parents id. + */ + public static JdbcIdentifierBuilder forBackReferences(JdbcConverter converter, AggregatePath path, + @Nullable Object value) { Identifier identifier = Identifier.of( // - path.getReverseColumnName(), // + AggregatePathUtil.getReverseColumnName(path), // value, // - converter.getColumnType(path.getIdDefiningParentPath().getRequiredIdProperty()) // + converter.getColumnType(AggregatePathUtil.getIdDefiningParentPath(path).getRequiredIdProperty()) // ); return new JdbcIdentifierBuilder(identifier); @@ -59,12 +73,29 @@ public static JdbcIdentifierBuilder forBackReferences(JdbcConverter converter, P * @param value map key or list index qualifying the map identified by {@code path}. Must not be {@literal null}. * @return this builder. Guaranteed to be not {@literal null}. */ + @Deprecated public JdbcIdentifierBuilder withQualifier(PersistentPropertyPathExtension path, Object value) { + return withQualifier(path.getAggregatePath(), value); + } + + /** + * Adds a qualifier to the identifier to build. A qualifier is a map key or a list index. + * + * @param path path to the map that gets qualified by {@code value}. Must not be {@literal null}. + * @param value map key or list index qualifying the map identified by {@code path}. Must not be {@literal null}. + * @return this builder. Guaranteed to be not {@literal null}. + */ + public JdbcIdentifierBuilder withQualifier(AggregatePath path, Object value) { + Assert.notNull(path, "Path must not be null"); Assert.notNull(value, "Value must not be null"); + Assert.isTrue(path.isQualified(), + () -> String.format("AggregatePath %s must be be a Map or Collection-like property", path)); - identifier = identifier.withPart(path.getQualifierColumn(), value, path.getQualifierColumnType()); + // TODO: What if the path is a root? + ForeignTableDetector ft = ForeignTableDetector.of(path); + identifier = identifier.withPart(ft.getQualifierColumn(), value, ft.getQualifierColumnType()); return this; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcPropertyValueProvider.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcPropertyValueProvider.java index f4cd5302bc..0a18565d13 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcPropertyValueProvider.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcPropertyValueProvider.java @@ -16,9 +16,9 @@ package org.springframework.data.jdbc.core.convert; import org.springframework.data.mapping.model.PropertyValueProvider; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.ColumnDetector; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.data.relational.core.sql.IdentifierProcessing; /** * {@link PropertyValueProvider} obtaining values from a {@link ResultSetAccessor}. @@ -29,15 +29,14 @@ */ class JdbcPropertyValueProvider implements PropertyValueProvider { - private final PersistentPropertyPathExtension basePath; + private final AggregatePath basePath; private final ResultSetAccessor resultSet; /** * @param basePath path from the aggregate root relative to which all properties get resolved. * @param resultSet the {@link ResultSetAccessor} from which to obtain the actual values. */ - JdbcPropertyValueProvider(PersistentPropertyPathExtension basePath, - ResultSetAccessor resultSet) { + JdbcPropertyValueProvider(AggregatePath basePath, ResultSetAccessor resultSet) { this.resultSet = resultSet; this.basePath = basePath; @@ -59,10 +58,10 @@ public boolean hasProperty(RelationalPersistentProperty property) { } private String getColumnName(RelationalPersistentProperty property) { - return basePath.extendBy(property).getColumnAlias().getReference(); + return ColumnDetector.of(basePath.append(property)).getColumnAlias().getReference(); } public JdbcPropertyValueProvider extendBy(RelationalPersistentProperty property) { - return new JdbcPropertyValueProvider(basePath.extendBy(property), resultSet); + return new JdbcPropertyValueProvider(basePath.append(property), resultSet); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java index 7c20d43b05..39e70e2ab9 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; +import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -35,12 +36,13 @@ */ class MapEntityRowMapper implements RowMapper> { - private final PersistentPropertyPathExtension path; + private final AggregatePath path; private final JdbcConverter converter; private final Identifier identifier; private final SqlIdentifier keyColumn; - MapEntityRowMapper(PersistentPropertyPathExtension path, JdbcConverter converter, Identifier identifier, SqlIdentifier keyColumn) { + MapEntityRowMapper(AggregatePath path, JdbcConverter converter, Identifier identifier, SqlIdentifier keyColumn) { + this.path = path; this.converter = converter; this.identifier = identifier; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java index 97ac10ef0f..0cb84bd27f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java @@ -15,8 +15,11 @@ */ package org.springframework.data.jdbc.core.convert; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; +import org.springframework.data.relational.core.mapping.ColumnDetector; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.mapping.TableAccessor; import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; @@ -52,18 +55,24 @@ Table getTable() { return table; } - Table getTable(PersistentPropertyPathExtension path) { + Table getTable(AggregatePath path) { + return getTable(TableAccessor.of(path)); + } + + Table getTable(TableAccessor tableAccessor) { - SqlIdentifier tableAlias = path.getTableAlias(); - Table table = Table.create(path.getQualifiedTableName()); + SqlIdentifier tableAlias = tableAccessor.findTableAlias(); + Table table = Table.create(tableAccessor.getQualifiedTableName()); return tableAlias == null ? table : table.as(tableAlias); } - Column getColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getColumnName()).as(path.getColumnAlias()); + Column getColumn(AggregatePath path) { + ColumnDetector detector = ColumnDetector.of(path); + return getTable(path).column(detector.getColumnName()).as(detector.getColumnAlias()); } - Column getReverseColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getReverseColumnName()).as(path.getReverseColumnNameAlias()); + Column getReverseColumn(AggregatePath path) { + return getTable(path).column(AggregatePathUtil.getReverseColumnName(path)) + .as(AggregatePathUtil.getReverseColumnNameAlias(path)); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java index cf1b85372c..1c6a93078d 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java @@ -26,7 +26,10 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.RenderContextFactory; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; +import org.springframework.data.relational.core.mapping.ColumnDetector; +import org.springframework.data.relational.core.mapping.ForeignTableDetector; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -67,7 +70,7 @@ class SqlGenerator { static final SqlIdentifier ROOT_ID_PARAMETER = SqlIdentifier.unquoted("rootId"); private final RelationalPersistentEntity entity; - private final MappingContext, RelationalPersistentProperty> mappingContext; + private final RelationalMappingContext mappingContext; private final RenderContext renderContext; private final SqlContext sqlContext; @@ -121,21 +124,23 @@ class SqlGenerator { * @param filterColumn the column to apply the IN-condition to. * @return the IN condition */ - private Condition getSubselectCondition(PersistentPropertyPathExtension path, - Function rootCondition, Column filterColumn) { + private Condition getSubselectCondition(AggregatePath path, Function rootCondition, + Column filterColumn) { - PersistentPropertyPathExtension parentPath = path.getParentPath(); + AggregatePath parentPath = path.getParentPath(); - if (!parentPath.hasIdProperty()) { + if (!AggregatePathUtil.hasIdProperty(parentPath)) { if (parentPath.getLength() > 1) { return getSubselectCondition(parentPath, rootCondition, filterColumn); } return rootCondition.apply(filterColumn); } - Table subSelectTable = Table.create(parentPath.getQualifiedTableName()); - Column idColumn = subSelectTable.column(parentPath.getIdColumnName()); - Column selectFilterColumn = subSelectTable.column(parentPath.getEffectiveIdColumnName()); + ColumnDetector detector = ColumnDetector.of(parentPath); + + Table subSelectTable = Table.create(detector.getQualifiedTableName()); + Column idColumn = subSelectTable.column(detector.getIdColumnName()); + Column selectFilterColumn = subSelectTable.column(detector.getEffectiveIdColumnName()); Condition innerCondition; @@ -216,9 +221,9 @@ String getFindAllByProperty(Identifier parentIdentifier, Assert.notNull(parentIdentifier, "identifier must not be null"); Assert.notNull(propertyPath, "propertyPath must not be null"); - PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(mappingContext, propertyPath); + AggregatePath path = mappingContext.getAggregatePath(propertyPath); - return getFindAllByProperty(parentIdentifier, path.getQualifierColumn(), path.isOrdered()); + return getFindAllByProperty(parentIdentifier, ForeignTableDetector.of(path).getQualifierColumn(), path.isOrdered()); } /** @@ -399,7 +404,7 @@ String createDeleteAllSql(@Nullable PersistentPropertyPath path) { - return createDeleteByPathAndCriteria(new PersistentPropertyPathExtension(mappingContext, path), + return createDeleteByPathAndCriteria(mappingContext.getAggregatePath(path), filterColumn -> filterColumn.isEqualTo(getBindMarker(ROOT_ID_PARAMETER))); } @@ -423,7 +428,7 @@ String createDeleteByPath(PersistentPropertyPath p */ String createDeleteInByPath(PersistentPropertyPath path) { - return createDeleteByPathAndCriteria(new PersistentPropertyPathExtension(mappingContext, path), + return createDeleteByPathAndCriteria(mappingContext.getAggregatePath(path), filterColumn -> filterColumn.in(getBindMarker(IDS_SQL_PARAMETER))); } @@ -480,7 +485,7 @@ private SelectBuilder.SelectWhere selectBuilder(Collection keyCol for (PersistentPropertyPath path : mappingContext .findPersistentPropertyPaths(entity.getType(), p -> true)) { - PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(mappingContext, path); + AggregatePath extPath = mappingContext.getAggregatePath(path); // add a join if necessary Join join = getJoin(extPath); @@ -537,13 +542,13 @@ private SelectBuilder.SelectOrdered applyPagination(Pageable pageable, SelectBui } /** - * Create a {@link Column} for {@link PersistentPropertyPathExtension}. + * Create a {@link Column} for {@link AggregatePath}. * * @param path the path to the column in question. * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ @Nullable - Column getColumn(PersistentPropertyPathExtension path) { + Column getColumn(AggregatePath path) { // an embedded itself doesn't give a column, its members will though. // if there is a collection or map on the path it won't get selected at all, but it will get loaded with a separate @@ -560,7 +565,7 @@ Column getColumn(PersistentPropertyPathExtension path) { if (path.isQualified() // || path.isCollectionLike() // - || path.hasIdProperty() // + || AggregatePathUtil.hasIdProperty(path) // ) { return null; } @@ -572,21 +577,22 @@ Column getColumn(PersistentPropertyPathExtension path) { } @Nullable - Join getJoin(PersistentPropertyPathExtension path) { + Join getJoin(AggregatePath path) { if (!path.isEntity() || path.isEmbedded() || path.isMultiValued()) { return null; } - Table currentTable = sqlContext.getTable(path); + ForeignTableDetector foreignDetector = ForeignTableDetector.of(path); + ColumnDetector ownerDetector = ColumnDetector.of(foreignDetector.getTableOwner()); - PersistentPropertyPathExtension idDefiningParentPath = path.getIdDefiningParentPath(); - Table parentTable = sqlContext.getTable(idDefiningParentPath); + Table joinTable = sqlContext.getTable(foreignDetector); + Table owner = sqlContext.getTable(ownerDetector); return new Join( // - currentTable, // - currentTable.column(path.getReverseColumnName()), // - parentTable.column(idDefiningParentPath.getIdColumnName()) // + joinTable, // + joinTable.column(foreignDetector.getReverseColumnName()), // + owner.column(ownerDetector.getIdColumnName()) // ); } @@ -707,16 +713,17 @@ private DeleteBuilder.DeleteWhereAndOr createBaseDeleteByIdIn(Table table) { .where(getIdColumn().in(getBindMarker(IDS_SQL_PARAMETER))); } - private String createDeleteByPathAndCriteria(PersistentPropertyPathExtension path, - Function rootCondition) { + private String createDeleteByPathAndCriteria(AggregatePath path, Function rootCondition) { + + ColumnDetector detector = ColumnDetector.of(path); - Table table = Table.create(path.getQualifiedTableName()); + Table table = Table.create(detector.getQualifiedTableName()); DeleteBuilder.DeleteWhere builder = Delete.builder() // .from(table); Delete delete; - Column filterColumn = table.column(path.getReverseColumnName()); + Column filterColumn = table.column(ForeignTableDetector.of(path).getReverseColumnName()); if (path.getLength() == 1) { @@ -926,10 +933,10 @@ private SelectBuilder.SelectJoin getExistsSelect() { for (PersistentPropertyPath path : mappingContext .findPersistentPropertyPaths(entity.getType(), p -> true)) { - PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(mappingContext, path); + AggregatePath aggregatePath = mappingContext.getAggregatePath(path); // add a join if necessary - Join join = getJoin(extPath); + Join join = getJoin(aggregatePath); if (join != null) { baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId); } @@ -960,7 +967,7 @@ private SelectBuilder.SelectJoin getSelectCountWithExpression(Expression... coun for (PersistentPropertyPath path : mappingContext .findPersistentPropertyPaths(entity.getType(), p -> true)) { - PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(mappingContext, path); + AggregatePath extPath = mappingContext.getAggregatePath(path); // add a join if necessary Join join = getJoin(extPath); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java index 075d67a711..fc67f08e1d 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java @@ -25,10 +25,12 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.QueryMapper; import org.springframework.data.mapping.PersistentPropertyPath; -import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.RenderContextFactory; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; +import org.springframework.data.relational.core.mapping.ColumnDetector; +import org.springframework.data.relational.core.mapping.ForeignTableDetector; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -117,8 +119,7 @@ class JdbcQueryCreator extends RelationalQueryCreator { * @param tree the tree structure defining the predicate of the query. * @param parameters parameters for the predicate. */ - static void validate(PartTree tree, Parameters parameters, - MappingContext, ? extends RelationalPersistentProperty> context) { + static void validate(PartTree tree, Parameters parameters, RelationalMappingContext context) { RelationalQueryCreator.validate(tree, parameters); @@ -127,30 +128,28 @@ static void validate(PartTree tree, Parameters parameters, PersistentPropertyPath propertyPath = context .getPersistentPropertyPath(part.getProperty()); - PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, propertyPath); + AggregatePath path = context.getAggregatePath(propertyPath); - for (PersistentPropertyPathExtension pathToValidate = path; path.getLength() > 0; path = path.getParentPath()) { + for (AggregatePath pathToValidate = path; path.getLength() > 0; path = path.getParentPath()) { validateProperty(pathToValidate); } } } } - private static void validateProperty(PersistentPropertyPathExtension path) { + private static void validateProperty(AggregatePath path) { if (!path.getParentPath().isEmbedded() && path.getLength() > 1) { - throw new IllegalArgumentException( - String.format("Cannot query by nested property: %s", path.getRequiredPersistentPropertyPath().toDotPath())); + throw new IllegalArgumentException(String.format("Cannot query by nested property: %s", path.toDotPath())); } if (path.isMultiValued() || path.isMap()) { - throw new IllegalArgumentException(String.format("Cannot query by multi-valued property: %s", - path.getRequiredPersistentPropertyPath().getLeafProperty().getName())); + throw new IllegalArgumentException( + String.format("Cannot query by multi-valued property: %s", path.getRequiredLeafProperty().getName())); } if (!path.isEmbedded() && path.isEntity()) { - throw new IllegalArgumentException( - String.format("Cannot query by nested entity: %s", path.getRequiredPersistentPropertyPath().toDotPath())); + throw new IllegalArgumentException(String.format("Cannot query by nested entity: %s", path.toDotPath())); } } @@ -245,22 +244,21 @@ private SelectBuilder.SelectJoin selectBuilder(Table table) { for (PersistentPropertyPath path : context .findPersistentPropertyPaths(entity.getType(), p -> true)) { - PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(context, path); + AggregatePath aggregatePath = context.getAggregatePath(path); if (returnedType.needsCustomConstruction()) { - if (!returnedType.getInputProperties() - .contains(extPath.getRequiredPersistentPropertyPath().getBaseProperty().getName())) { + if (!returnedType.getInputProperties().contains(aggregatePath.getBaseProperty().getName())) { continue; } } // add a join if necessary - Join join = getJoin(sqlContext, extPath); + Join join = getJoin(sqlContext, aggregatePath); if (join != null) { joinTables.add(join); } - Column column = getColumn(sqlContext, extPath); + Column column = getColumn(sqlContext, aggregatePath); if (column != null) { columnExpressions.add(column); } @@ -277,14 +275,14 @@ private SelectBuilder.SelectJoin selectBuilder(Table table) { } /** - * Create a {@link Column} for {@link PersistentPropertyPathExtension}. + * Create a {@link Column} for {@link AggregatePath}. * * @param sqlContext * @param path the path to the column in question. * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. */ @Nullable - private Column getColumn(SqlContext sqlContext, PersistentPropertyPathExtension path) { + private Column getColumn(SqlContext sqlContext, AggregatePath path) { // an embedded itself doesn't give an column, its members will though. // if there is a collection or map on the path it won't get selected at all, but it will get loaded with a separate @@ -301,7 +299,7 @@ private Column getColumn(SqlContext sqlContext, PersistentPropertyPathExtension if (path.isQualified() // || path.isCollectionLike() // - || path.hasIdProperty() // + || AggregatePathUtil.hasIdProperty(path) // ) { return null; } @@ -313,7 +311,7 @@ private Column getColumn(SqlContext sqlContext, PersistentPropertyPathExtension } @Nullable - Join getJoin(SqlContext sqlContext, PersistentPropertyPathExtension path) { + Join getJoin(SqlContext sqlContext, AggregatePath path) { if (!path.isEntity() || path.isEmbedded() || path.isMultiValued()) { return null; @@ -321,13 +319,14 @@ Join getJoin(SqlContext sqlContext, PersistentPropertyPathExtension path) { Table currentTable = sqlContext.getTable(path); - PersistentPropertyPathExtension idDefiningParentPath = path.getIdDefiningParentPath(); + AggregatePath idDefiningParentPath = AggregatePathUtil.getIdDefiningParentPath(path); + ColumnDetector parentDetector = ColumnDetector.of(path); Table parentTable = sqlContext.getTable(idDefiningParentPath); return new Join( // currentTable, // - currentTable.column(path.getReverseColumnName()), // - parentTable.column(idDefiningParentPath.getIdColumnName()) // + currentTable.column(ForeignTableDetector.of(path).getReverseColumnName()), // + parentTable.column(parentDetector.getIdColumnName()) // ); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java index b431610815..9cf28ac512 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java @@ -15,10 +15,12 @@ */ package org.springframework.data.jdbc.repository.query; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; +import org.springframework.data.relational.core.mapping.ColumnDetector; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.mapping.TableAccessor; import org.springframework.data.relational.core.sql.Column; -import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; /** @@ -53,18 +55,27 @@ Table getTable() { return table; } - Table getTable(PersistentPropertyPathExtension path) { + Table getTable(AggregatePath path) { - SqlIdentifier tableAlias = path.getTableAlias(); - Table table = Table.create(path.getQualifiedTableName()); - return tableAlias == null ? table : table.as(tableAlias); + TableAccessor accessor = TableAccessor.of(path); + Table table = Table.create(accessor.getQualifiedTableName()); + + if (accessor.hasTableAlias()) { + return table.as(accessor.findTableAlias()); + } + + return table; } - Column getColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getColumnName()).as(path.getColumnAlias()); + Column getColumn(AggregatePath path) { + + ColumnDetector detector = ColumnDetector.of(path); + + return getTable(path).column(detector.getColumnName()).as(detector.getColumnAlias()); } - Column getReverseColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getReverseColumnName()).as(path.getReverseColumnNameAlias()); + Column getReverseColumn(AggregatePath path) { + return getTable(path).column(AggregatePathUtil.getReverseColumnName(path)) + .as(AggregatePathUtil.getReverseColumnNameAlias(path)); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java index 031aaa2379..276a9eccc0 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java @@ -37,7 +37,7 @@ import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.IdValueSource; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.lang.Nullable; @@ -160,8 +160,8 @@ DbAction.Insert createInsert(DbAction.WithEntity parent, String propertyNa key == null ? emptyMap() : singletonMap(toPath(propertyName), key), IdValueSource.GENERATED); } - PersistentPropertyPathExtension toPathExt(String path) { - return new PersistentPropertyPathExtension(context, getPersistentPropertyPath(path)); + AggregatePath toAggregatePath(String path) { + return context.getAggregatePath(getPersistentPropertyPath(path)); } PersistentPropertyPath getPersistentPropertyPath(String propertyName) { @@ -169,7 +169,7 @@ PersistentPropertyPath getPersistentPropertyPath(S } Identifier createBackRef(long value) { - return JdbcIdentifierBuilder.forBackReferences(converter, toPathExt("content"), value).build(); + return JdbcIdentifierBuilder.forBackReferences(converter, toAggregatePath("content"), value).build(); } PersistentPropertyPath toPath(String path) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java index 439d850794..cd6930938c 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java @@ -36,7 +36,7 @@ import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.relational.core.conversion.DbAction; import org.springframework.data.relational.core.conversion.IdValueSource; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -250,8 +250,8 @@ DbAction.Insert createInsert(DbAction.WithEntity parent, String propertyNa key == null ? emptyMap() : singletonMap(toPath(propertyName), key), idValueSource); } - PersistentPropertyPathExtension toPathExt(String path) { - return new PersistentPropertyPathExtension(context, getPersistentPropertyPath(path)); + AggregatePath toAggregatePath(String path) { + return context.getAggregatePath(getPersistentPropertyPath(path)); } PersistentPropertyPath getPersistentPropertyPath(String propertyName) { @@ -259,7 +259,7 @@ PersistentPropertyPath getPersistentPropertyPath(S } Identifier createBackRef(long value) { - return forBackReferences(converter, toPathExt("content"), value).build(); + return forBackReferences(converter, toAggregatePath("content"), value).build(); } PersistentPropertyPath toPath(String path) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java index 49196a8a3c..0f65f6dd6c 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathExtensionUnitTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jdbc.core; +import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.SoftAssertions.*; import static org.springframework.data.relational.core.sql.SqlIdentifier.*; @@ -238,6 +239,14 @@ void isWritable() { }); } + @Test // GH-1525 + void getAggregatePath() { + assertThat(extPath("withId").getAggregatePath()).isNotNull(); + } + @Test // GH-1525 + void getAggregatePathFromRoot() { + assertThat(extPath(entity).getAggregatePath()).isNotNull(); + } private PersistentPropertyPathExtension extPath(RelationalPersistentEntity entity) { return new PersistentPropertyPathExtension(context, entity); } @@ -247,7 +256,7 @@ private PersistentPropertyPathExtension extPath(String path) { } PersistentPropertyPath createSimplePath(String path) { - return PropertyPathTestingUtils.toPath(path, DummyEntity.class, context); + return PersistentPropertyPathTestUtils.getPath(path, DummyEntity.class, context); } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathTestingUtils.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathTestUtils.java similarity index 83% rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathTestingUtils.java rename to spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathTestUtils.java index 8670a6720b..2c79040e23 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PropertyPathTestingUtils.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/PersistentPropertyPathTestUtils.java @@ -29,14 +29,18 @@ * @author Jens Schauder */ @UtilityClass -public class PropertyPathTestingUtils { +public class PersistentPropertyPathTestUtils { - public static PersistentPropertyPath toPath(String path, Class source, - RelationalMappingContext context) { + public static PersistentPropertyPath getPath(String path, Class source, + RelationalMappingContext context) { PersistentPropertyPaths persistentPropertyPaths = context .findPersistentPropertyPaths(source, p -> true); - return persistentPropertyPaths.filter(p -> p.toDotPath().equals(path)).stream().findFirst().orElse(null); + return persistentPropertyPaths + .filter(p -> p.toDotPath().equals(path)) + .stream() + .findFirst() + .orElse(null); } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java index b0a40e12a8..f8f237e430 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilderUnitTests.java @@ -16,7 +16,6 @@ package org.springframework.data.jdbc.core.convert; import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.jdbc.core.PropertyPathTestingUtils.*; import static org.springframework.data.relational.core.sql.SqlIdentifier.*; import java.util.List; @@ -25,8 +24,9 @@ import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePath; /** * Unit tests for the {@link JdbcIdentifierBuilder}. @@ -55,7 +55,7 @@ public void parametersWithPropertyKeysUseTheParentPropertyJdbcType() { @Test // DATAJDBC-326 public void qualifiersForMaps() { - PersistentPropertyPathExtension path = getPath("children"); + AggregatePath path = getPath("children"); Identifier identifier = JdbcIdentifierBuilder // .forBackReferences(converter, path, "parent-eins") // @@ -73,7 +73,7 @@ public void qualifiersForMaps() { @Test // DATAJDBC-326 public void qualifiersForLists() { - PersistentPropertyPathExtension path = getPath("moreChildren"); + AggregatePath path = getPath("moreChildren"); Identifier identifier = JdbcIdentifierBuilder // .forBackReferences(converter, path, "parent-eins") // @@ -116,8 +116,8 @@ public void backreferenceAcrossNoId() { ); } - private PersistentPropertyPathExtension getPath(String dotPath) { - return new PersistentPropertyPathExtension(context, toPath(dotPath, DummyEntity.class, context)); + private AggregatePath getPath(String dotPath) { + return context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(dotPath, DummyEntity.class, context)); } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java index 69849a3df1..87510b0a67 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorContextBasedNamingStrategyUnitTests.java @@ -26,8 +26,8 @@ import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; -import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; @@ -157,7 +157,7 @@ public void cascadingDeleteSecondLevel() { } private PersistentPropertyPath getPath(String path) { - return PersistentPropertyPathTestUtils.getPath(this.context, path, DummyEntity.class); + return PersistentPropertyPathTestUtils.getPath(path, DummyEntity.class, this.context); } /** diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java index 7d86bb86d2..25a97b85f6 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java @@ -23,12 +23,11 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; -import org.springframework.data.jdbc.core.PropertyPathTestingUtils; +import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.sql.Aliased; @@ -177,7 +176,7 @@ public void update() { public void deleteByPath() { final String sql = sqlGenerator - .createDeleteByPath(PropertyPathTestingUtils.toPath("embedded.other", DummyEntity2.class, context)); + .createDeleteByPath(PersistentPropertyPathTestUtils.getPath("embedded.other", DummyEntity2.class, context)); assertThat(sql).containsSequence("DELETE FROM other_entity", // "WHERE", // @@ -295,7 +294,7 @@ public void columnForEmbeddedWithReferenceProperty() { private SqlGenerator.Join generateJoin(String path, Class type) { return createSqlGenerator(type) - .getJoin(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); + .getJoin(context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(path, type, context))); } @Nullable @@ -310,13 +309,14 @@ private SqlIdentifier getAlias(Object maybeAliased) { private org.springframework.data.relational.core.sql.Column generatedColumn(String path, Class type) { return createSqlGenerator(type) - .getColumn(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); + .getColumn(context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(path, type, context))); } @SuppressWarnings("unused") static class DummyEntity { - @Column("id1") @Id Long id; + @Column("id1") + @Id Long id; @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix_") CascadedEmbedded prefixedEmbeddable; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java index 2cca7da3dd..5784dc0959 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java @@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; -import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.dialect.AnsiDialect; import org.springframework.data.relational.core.mapping.NamingStrategy; @@ -70,7 +70,7 @@ public String getColumnName(RelationalPersistentProperty property) { } }; - private RelationalMappingContext context = new JdbcMappingContext(); + private RelationalMappingContext context; @Test // DATAJDBC-107 void findOneWithOverriddenFixedTableName() { @@ -122,7 +122,7 @@ void cascadingDeleteFirstLevel() { String sql = sqlGenerator.createDeleteByPath(getPath("ref")); assertThat(sql).isEqualTo("DELETE FROM \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\" " - + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"DUMMY_ENTITY\" = :rootId"); + + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"FIXEDCUSTOMTABLEPREFIX_DUMMYENTITY\" = :rootId"); } @Test // DATAJDBC-107 @@ -137,7 +137,7 @@ void cascadingDeleteAllSecondLevel() { + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_SECONDLEVELREFERENCEDENTITY\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\" IN " + "(SELECT \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"FIXEDCUSTOMPROPERTYPREFIX_L1ID\" " + "FROM \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\" " - + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"DUMMY_ENTITY\" = :rootId)"); + + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"FIXEDCUSTOMTABLEPREFIX_DUMMYENTITY\" = :rootId)"); } @Test // DATAJDBC-107 @@ -158,7 +158,7 @@ void cascadingDeleteAllFirstLevel() { String sql = sqlGenerator.createDeleteAllSql(getPath("ref")); assertThat(sql).isEqualTo("DELETE FROM \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\" " - + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"DUMMY_ENTITY\" IS NOT NULL"); + + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"FIXEDCUSTOMTABLEPREFIX_DUMMYENTITY\" IS NOT NULL"); } @Test // DATAJDBC-107 @@ -173,7 +173,7 @@ void cascadingDeleteSecondLevel() { + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_SECONDLEVELREFERENCEDENTITY\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\" IN " + "(SELECT \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"FIXEDCUSTOMPROPERTYPREFIX_L1ID\" " + "FROM \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\" " - + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"DUMMY_ENTITY\" IS NOT NULL)"); + + "WHERE \"FIXEDCUSTOMSCHEMA\".\"FIXEDCUSTOMTABLEPREFIX_REFERENCEDENTITY\".\"FIXEDCUSTOMTABLEPREFIX_DUMMYENTITY\" IS NOT NULL)"); } @Test // DATAJDBC-113 @@ -188,7 +188,7 @@ void deleteByList() { } private PersistentPropertyPath getPath(String path) { - return PersistentPropertyPathTestUtils.getPath(context, path, DummyEntity.class); + return PersistentPropertyPathTestUtils.getPath(path, DummyEntity.class, context); } /** @@ -196,7 +196,7 @@ private PersistentPropertyPath getPath(String path */ private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { - RelationalMappingContext context = new JdbcMappingContext(namingStrategy); + context = new JdbcMappingContext(namingStrategy); JdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> { throw new UnsupportedOperationException(); }); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java index 2349be4aa3..5be8464131 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java @@ -32,10 +32,9 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.jdbc.core.PropertyPathTestingUtils; +import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; -import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.dialect.AnsiDialect; import org.springframework.data.relational.core.dialect.Dialect; @@ -736,7 +735,7 @@ void joinForOneToOneWithoutId() { @Nullable private SqlGenerator.Join generateJoin(String path, Class type) { return createSqlGenerator(type, AnsiDialect.INSTANCE) - .getJoin(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); + .getJoin(context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(path, type, context))); } @Test // DATAJDBC-340 @@ -934,12 +933,12 @@ private SqlIdentifier getAlias(Object maybeAliased) { @Nullable private org.springframework.data.relational.core.sql.Column generatedColumn(String path, Class type) { - return createSqlGenerator(type, AnsiDialect.INSTANCE) - .getColumn(new PersistentPropertyPathExtension(context, PropertyPathTestingUtils.toPath(path, type, context))); + return createSqlGenerator(type, AnsiDialect.INSTANCE).getColumn( + context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(path, type, context))); } private PersistentPropertyPath getPath(String path, Class baseType) { - return PersistentPropertyPathTestUtils.getPath(context, path, baseType); + return PersistentPropertyPathTestUtils.getPath(path, baseType, context); } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java index 499d9da0df..b6fbdd6c4f 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/BasicJdbcPersistentPropertyUnitTests.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.BasicRelationalPersistentProperty; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.MappedCollection; @@ -65,7 +66,7 @@ public void detectsAnnotatedColumnAndKeyName() { String propertyName = "someList"; RelationalPersistentProperty listProperty = entity.getRequiredPersistentProperty(propertyName); - PersistentPropertyPathExtension path = getPersistentPropertyPath(DummyEntity.class, propertyName); + AggregatePath path = getPersistentPropertyPath(DummyEntity.class, propertyName); assertThat(listProperty.getReverseColumnName(path)).isEqualTo(quoted("dummy_column_name")); assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("dummy_key_column_name")); @@ -78,7 +79,7 @@ public void detectsReverseColumnNameFromColumnAnnotation() { RelationalPersistentProperty listProperty = context // .getRequiredPersistentEntity(WithCollections.class) // .getRequiredPersistentProperty(propertyName); - PersistentPropertyPathExtension path = getPersistentPropertyPath(DummyEntity.class, propertyName); + AggregatePath path = getPersistentPropertyPath(DummyEntity.class, propertyName); assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("WITH_COLLECTIONS_KEY")); assertThat(listProperty.getReverseColumnName(path)).isEqualTo(quoted("some_value")); @@ -90,7 +91,7 @@ public void detectsKeyColumnOverrideNameFromMappedCollectionAnnotation() { RelationalPersistentProperty listProperty = context // .getRequiredPersistentEntity(WithCollections.class) // .getRequiredPersistentProperty("overrideList"); - PersistentPropertyPathExtension path = getPersistentPropertyPath(WithCollections.class, "overrideList"); + AggregatePath path = getPersistentPropertyPath(WithCollections.class, "overrideList"); assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("override_key")); assertThat(listProperty.getReverseColumnName(path)).isEqualTo(quoted("override_id")); @@ -128,12 +129,13 @@ void considersAggregateReferenceAnAssociation() { }); } - private PersistentPropertyPathExtension getPersistentPropertyPath(Class type, String propertyName) { + private AggregatePath getPersistentPropertyPath(Class type, String propertyName) { + PersistentPropertyPath path = context .findPersistentPropertyPaths(type, p -> p.getName().equals(propertyName)).getFirst() .orElseThrow(() -> new AssertionFailedError(String.format("Couldn't find path for '%s'", propertyName))); - return new PersistentPropertyPathExtension(context, path); + return context.getAggregatePath( path); } @SuppressWarnings("unused") diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java index 4f1a26e1e8..c0c5ef5f14 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategyUnitTests.java @@ -28,7 +28,7 @@ import org.mockito.ArgumentCaptor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.springframework.data.jdbc.core.PropertyPathTestingUtils; +import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils; import org.springframework.data.jdbc.core.convert.Identifier; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.mapping.PersistentPropertyPath; @@ -54,7 +54,7 @@ public class MyBatisDataAccessStrategyUnitTests { MyBatisDataAccessStrategy accessStrategy = new MyBatisDataAccessStrategy(session, IdentifierProcessing.ANSI); - PersistentPropertyPath path = PropertyPathTestingUtils.toPath("one.two", + PersistentPropertyPath path = PersistentPropertyPathTestUtils.getPath("one.two", DummyEntity.class, context); @BeforeEach diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index 4eb3a50392..ed86b72fea 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 3.2.0-SNAPSHOT + 3.2.0-refactor-path-extension-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 3.2.0-SNAPSHOT + 3.2.0-refactor-path-extension-SNAPSHOT diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java index afe50d096b..b1810a2309 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java @@ -50,6 +50,7 @@ import org.springframework.data.relational.core.conversion.BasicRelationalConverter; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.dialect.ArrayColumns; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.util.TypeInformation; @@ -74,7 +75,7 @@ public class MappingR2dbcConverter extends BasicRelationalConverter implements R */ public MappingR2dbcConverter( MappingContext, ? extends RelationalPersistentProperty> context) { - super(context, new R2dbcCustomConversions(R2dbcCustomConversions.STORE_CONVERSIONS, Collections.emptyList())); + super((RelationalMappingContext) context, new R2dbcCustomConversions(R2dbcCustomConversions.STORE_CONVERSIONS, Collections.emptyList())); } /** @@ -85,7 +86,7 @@ public MappingR2dbcConverter( public MappingR2dbcConverter( MappingContext, ? extends RelationalPersistentProperty> context, CustomConversions conversions) { - super(context, conversions); + super((RelationalMappingContext) context, conversions); } // ---------------------------------- diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 57b9d707a6..e7e0d9e027 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-relational - 3.2.0-SNAPSHOT + 3.2.0-refactor-path-extension-SNAPSHOT Spring Data Relational Spring Data Relational support @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 3.2.0-SNAPSHOT + 3.2.0-refactor-path-extension-SNAPSHOT diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java index 710438ffd3..9573982e49 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java @@ -38,6 +38,7 @@ import org.springframework.data.mapping.model.EntityInstantiators; import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.util.TypeInformation; @@ -60,7 +61,7 @@ */ public class BasicRelationalConverter implements RelationalConverter { - private final MappingContext, RelationalPersistentProperty> context; + private final RelationalMappingContext context; private final ConfigurableConversionService conversionService; private final EntityInstantiators entityInstantiators; private final CustomConversions conversions; @@ -70,8 +71,7 @@ public class BasicRelationalConverter implements RelationalConverter { * * @param context must not be {@literal null}. */ - public BasicRelationalConverter( - MappingContext, ? extends RelationalPersistentProperty> context) { + public BasicRelationalConverter(RelationalMappingContext context) { this(context, new CustomConversions(StoreConversions.NONE, Collections.emptyList()), new DefaultConversionService(), new EntityInstantiators()); } @@ -82,22 +82,18 @@ public BasicRelationalConverter( * @param context must not be {@literal null}. * @param conversions must not be {@literal null}. */ - public BasicRelationalConverter( - MappingContext, ? extends RelationalPersistentProperty> context, - CustomConversions conversions) { + public BasicRelationalConverter(RelationalMappingContext context, CustomConversions conversions) { this(context, conversions, new DefaultConversionService(), new EntityInstantiators()); } @SuppressWarnings("unchecked") - private BasicRelationalConverter( - MappingContext, ? extends RelationalPersistentProperty> context, - CustomConversions conversions, ConfigurableConversionService conversionService, - EntityInstantiators entityInstantiators) { + private BasicRelationalConverter(RelationalMappingContext context, CustomConversions conversions, + ConfigurableConversionService conversionService, EntityInstantiators entityInstantiators) { Assert.notNull(context, "MappingContext must not be null"); Assert.notNull(conversions, "CustomConversions must not be null"); - this.context = (MappingContext) context; + this.context = context; this.conversionService = conversionService; this.entityInstantiators = entityInstantiators; this.conversions = conversions; @@ -115,7 +111,7 @@ public CustomConversions getConversions() { } @Override - public MappingContext, ? extends RelationalPersistentProperty> getMappingContext() { + public RelationalMappingContext getMappingContext() { return context; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java index 0e8f46b67b..c2931bc797 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java @@ -22,7 +22,7 @@ import org.springframework.data.convert.EntityWriter; import org.springframework.data.mapping.PersistentPropertyPath; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.lang.Nullable; @@ -126,7 +126,7 @@ private void forAllTableRepresentingPaths(Class entityType, Consumer> pathConsumer) { context.findPersistentPropertyPaths(entityType, property -> property.isEntity() && !property.isEmbedded()) // - .filter(PersistentPropertyPathExtension::isWritable) // + .filter(AggregatePathUtil::isWritable) // .forEach(pathConsumer); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java index b0ea0c1768..ce170bd5f8 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java @@ -24,7 +24,7 @@ import java.util.Map; import org.springframework.data.mapping.PersistentPropertyPath; -import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.AggregatePathUtil; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -63,7 +63,7 @@ class WritingContext { this.rootIdValueSource = IdValueSource.forInstance(root, context.getRequiredPersistentEntity(aggregateChange.getEntityType())); this.paths = context.findPersistentPropertyPaths(entityType, (p) -> p.isEntity() && !p.isEmbedded()) // - .filter(PersistentPropertyPathExtension::isWritable).toList(); + .filter(AggregatePathUtil::isWritable).toList(); } /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java new file mode 100644 index 0000000000..c1ecdb6009 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java @@ -0,0 +1,155 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.relational.core.mapping; + +import java.util.function.Predicate; + +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.lang.Nullable; + +/** + * Represents a path within an aggregate starting from the aggregate root. + *

+ * The path implements {@link Iterable} to iterate over all path segments including the path root. + * + * @since 3.2 + * @author Jens Schauder + * @author Mark Paluch + */ +public interface AggregatePath extends Iterable { + + /** + * Returns {@code true} if the current path is a root path element (i.e. {@link #getLength()} equals zero) or + * {@code false} if the path points to a leaf property. + * + * @return {@code true} if the current path is a root path element or {@code false} if the path points to a leaf + * property. + */ + boolean isRoot(); + + /** + * Returns the path that has the same beginning but is one segment shorter than this path. + * + * @return the parent path. Guaranteed to be not {@literal null}. + * @throws IllegalStateException when called on an empty path. + */ + AggregatePath getParentPath(); + + /** + * The {@link RelationalPersistentEntity} associated with the leaf of this path. + * + * @return Might return {@literal null} when called on a path that does not represent an entity. + */ + @Nullable + RelationalPersistentEntity getLeafEntity(); + + /** + * The {@link RelationalPersistentEntity} associated with the leaf of this path or throw {@link IllegalStateException} + * if the leaf cannot be resolved. + * + * @return the required {@link RelationalPersistentEntity} associated with the leaf of this path. + * @throws IllegalStateException if the persistent entity cannot be resolved. + */ + RelationalPersistentEntity getRequiredLeafEntity(); + + RelationalPersistentProperty getRequiredIdProperty(); + + int getLength(); + + /** + * Returns {@literal true} exactly when the path is non-empty and the leaf property an embedded one. + * + * @return if the leaf property is embedded. + */ + boolean isEmbedded(); + + /** + * @return {@literal true} when this is an empty path or the path references an entity. + */ + boolean isEntity(); + + String toDotPath(); + + /** + * Returns {@literal true} if there are multiple values for this path, i.e. if the path contains at least one element + * that is a collection and array or a map. + * + * @return {@literal true} if the path contains a multivalued element. + */ + boolean isMultiValued(); + + /** + * @return {@literal true} if the leaf property of this path is a {@link java.util.Map}. + * @see RelationalPersistentProperty#isMap() + */ + boolean isMap(); + + /** + * @return {@literal true} when this is references a {@link java.util.List} or {@link java.util.Map}. + */ + boolean isQualified(); + + RelationalPersistentProperty getRequiredLeafProperty(); + + RelationalPersistentProperty getBaseProperty(); + + /** + * @return {@literal true} when this is references a {@link java.util.Collection} or an array. + */ + boolean isCollectionLike(); + + /** + * @return whether the leaf end of the path is ordered, i.e. the data to populate must be ordered. + * @see RelationalPersistentProperty#isOrdered() + */ + boolean isOrdered(); + + /** + * Creates a new path by extending the current path by the property passed as an argument. + * + * @param property must not be {@literal null}. + * @return Guaranteed to be not {@literal null}. + */ + AggregatePath append(RelationalPersistentProperty property); + + PersistentPropertyPath getRequiredPersistentPropertyPath(); + + /** + * Filter the {@link AggregatePath} hierarchy by walking all path segment from the leaf to {@link #isRoot() root} + * applying the given filter {@link Predicate}. Returns a matching {@link AggregatePath} or {@literal null} if the + * filter predicate does not match any path segment. + * + * @param predicate + * @return the matched aggregate path element or {@code null} if the filter predicate does not match any path segment. + */ + @Nullable + default AggregatePath filter(Predicate predicate) { + + AggregatePath path = this; + while (!predicate.test(path)) { + + if (path.isRoot()) { + path = null; + break; + } + path = path.getParentPath(); + } + + return path; + } + +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathUtil.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathUtil.java new file mode 100644 index 0000000000..bc6cb15106 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathUtil.java @@ -0,0 +1,165 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.relational.core.mapping; + +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +public final class AggregatePathUtil { + + private AggregatePathUtil() { + throw new IllegalStateException("This class should never get instantiated"); + } + + public static boolean hasIdProperty(AggregatePath path) { + + RelationalPersistentEntity leafEntity = path.getLeafEntity(); + return leafEntity != null && leafEntity.hasIdProperty(); + } + + /** + * Returns the longest ancestor path that has an {@link org.springframework.data.annotation.Id} property. + * + * @return A path that starts just as this path but is shorter. Guaranteed to be not {@literal null}. + */ + public static AggregatePath getIdDefiningParentPath(AggregatePath path) { + + // TODO: What if the path is a root and the filter method returns null? + return path.getParentPath().filter(AggregatePathUtil::hasIdProperty); + } + + /** + * If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse + * column is returned. + */ + public static SqlIdentifier getEffectiveIdColumnName(AggregatePath path) { + + AggregatePath owner = getTableOwningAncestor(path); + return owner.isRoot() ? owner.getRequiredLeafEntity().getIdColumn() : getReverseColumnName(owner); + } + + /** + * The alias used in select for the column used to reference the id in the parent table. + * + * @throws IllegalStateException when called on an empty path. + */ + public static SqlIdentifier getReverseColumnNameAlias(AggregatePath path) { + + return prefixWithTableAlias(path, getReverseColumnName(path)); + } + + /** + * The name of the column used to reference the id in the parent table. + * + * @throws IllegalStateException when called on an empty path. + */ + public static SqlIdentifier getReverseColumnName(AggregatePath path) { + + Assert.state(!path.isRoot(), "Empty paths don't have a reverse column name"); + + return path.getRequiredLeafProperty().getReverseColumnName(path); + } + + /** + * Finds and returns the longest path with ich identical or an ancestor to the current path and maps directly to a + * table. + * + * @return a path. Guaranteed to be not {@literal null}. + */ + private static AggregatePath getTableOwningAncestor(AggregatePath path) { + return path.isEntity() && !path.isEmbedded() ? path : getTableOwningAncestor(path.getParentPath()); + } + + @Nullable + private static SqlIdentifier assembleTableAlias(AggregatePath path) { + + Assert.state(!path.isRoot(), "Path is null"); + + RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty(); + String prefix; + if (path.isEmbedded()) { + prefix = leafProperty.getEmbeddedPrefix(); + + } else { + prefix = leafProperty.getName(); + } + + if (path.getLength() == 1) { + Assert.notNull(prefix, "Prefix mus not be null"); + return StringUtils.hasText(prefix) ? SqlIdentifier.quoted(prefix) : null; + } + + AggregatePath parentPath = path.getParentPath(); + SqlIdentifier sqlIdentifier = assembleTableAlias(parentPath); + + if (sqlIdentifier != null) { + + return parentPath.isEmbedded() ? sqlIdentifier.transform(name -> name.concat(prefix)) + : sqlIdentifier.transform(name -> name + "_" + prefix); + } + return SqlIdentifier.quoted(prefix); + + } + + /** + * The alias used for the table on which this path is based. + * + * @return a table alias, {@literal null} if the table owning path is the empty path. + */ + @Nullable + private static SqlIdentifier findTableAlias(AggregatePath path) { + + AggregatePath tableOwner = getTableOwningAncestor(path); + + return tableOwner.isRoot() ? null : assembleTableAlias(tableOwner); + + } + + private static SqlIdentifier assembleColumnName(AggregatePath path, SqlIdentifier suffix) { + + Assert.state(!path.isRoot(), "Path is null"); + + if (path.getLength() <= 1) { + return suffix; + } + + PersistentPropertyPath parentPath = path.getParentPath() + .getRequiredPersistentPropertyPath(); + RelationalPersistentProperty parentLeaf = parentPath.getLeafProperty(); + + if (!parentLeaf.isEmbedded()) { + return suffix; + } + + String embeddedPrefix = parentLeaf.getEmbeddedPrefix(); + + return assembleColumnName(path.getParentPath(), suffix.transform(embeddedPrefix::concat)); + } + + private static SqlIdentifier prefixWithTableAlias(AggregatePath path, SqlIdentifier columnName) { + + SqlIdentifier tableAlias = findTableAlias(path); + return tableAlias == null ? columnName : columnName.transform(name -> tableAlias.getReference() + "_" + name); + } + + public static boolean isWritable(@Nullable PersistentPropertyPath path) { + return path == null || path.getLeafProperty().isWritable() && isWritable(path.getParentPath()); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java index f15abc92ef..ce1f3d6219 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java @@ -225,6 +225,12 @@ public SqlIdentifier getReverseColumnName(PersistentPropertyPathExtension path) return createSqlIdentifier(expressionEvaluator.evaluate(collectionIdColumnNameExpression)); } + @Override + public SqlIdentifier getReverseColumnName(AggregatePath path) { + return collectionIdColumnName.get() + .orElseGet(() -> createDerivedSqlIdentifier(this.namingStrategy.getReverseColumnName(path))); + } + @Override public SqlIdentifier getKeyColumn() { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java index 496fa30e0c..56ee07bb35 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java @@ -62,6 +62,10 @@ public String getTableName(Class type) { return tableNames.computeIfAbsent(type, delegate::getTableName); } + @Override + public String getReverseColumnName(AggregatePath path) { + return delegate.getReverseColumnName(path); + } @Override public String getReverseColumnName(PersistentPropertyPathExtension path) { return delegate.getReverseColumnName(path); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ColumnDetector.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ColumnDetector.java new file mode 100644 index 0000000000..fd9cd1b031 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ColumnDetector.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping; + +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.util.Assert; + +/** + * @author Mark Paluch + */ +public class ColumnDetector extends TableAccessor { + + private final AggregatePath path; + + private ColumnDetector(AggregatePath path) { + super(path); + this.path = path; + } + + public static ColumnDetector of(AggregatePath path) { + return new ColumnDetector(path); + } + + public static ColumnDetector of(TableAccessor tableOwner) { + if (tableOwner instanceof ColumnDetector) { + return (ColumnDetector) tableOwner; + } + + return new ColumnDetector(tableOwner.getPath()); + } + + @Override + ColumnDetector createTableAccessor(AggregatePath path) { + return of(path); + } + + @Override + public ColumnDetector getTableOwner() { + return (ColumnDetector) super.getTableOwner(); + } + + /** + * The column name of the id column of the ancestor path that represents an actual table. + */ + public SqlIdentifier getIdColumnName() { + return getTableOwner().getPath().getRequiredLeafEntity().getIdColumn(); + } + + /** + * If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse + * column is returned. + */ + public SqlIdentifier getEffectiveIdColumnName() { + + AggregatePath owner = getTableOwner().getPath(); + return owner.isRoot() ? owner.getRequiredLeafEntity().getIdColumn() + : SingleColumnAggregatePath.of(owner).getReverseColumnName(); + } + + /** + * The name of the column used to represent this property in the database. + * + * @throws IllegalStateException when called on an empty path. + */ + public SqlIdentifier getColumnName() { + + Assert.state(!path.isRoot(), "Path is null"); + + return assembleColumnName(path, path.getRequiredLeafProperty().getColumnName()); + } + + /** + * The alias for the column used to represent this property in the database. + */ + public SqlIdentifier getColumnAlias() { + return prefixWithTableAlias(getColumnName()); + } + + private static SqlIdentifier assembleColumnName(AggregatePath path, SqlIdentifier suffix) { + + if (path.getLength() <= 1) { + return suffix; + } + + PersistentPropertyPath parentPath = path.getParentPath() + .getRequiredPersistentPropertyPath(); + RelationalPersistentProperty parentLeaf = parentPath.getLeafProperty(); + + if (!parentLeaf.isEmbedded()) { + return suffix; + } + + String embeddedPrefix = parentLeaf.getEmbeddedPrefix(); + + return assembleColumnName(path.getParentPath(), suffix.transform(embeddedPrefix::concat)); + } + +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java new file mode 100644 index 0000000000..7f4c952d1b --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java @@ -0,0 +1,308 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.relational.core.mapping; + +import java.util.Iterator; +import java.util.Objects; + +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Represents a path within an aggregate starting from the aggregate root. + * + * @since 3.2 + * @author Jens Schauder + */ +class DefaultAggregatePath implements AggregatePath { + + private final RelationalMappingContext context; + + private final @Nullable RelationalPersistentEntity rootType; + + private final @Nullable PersistentPropertyPath path; + + DefaultAggregatePath(RelationalMappingContext context, + PersistentPropertyPath path) { + + Assert.notNull(context, "context must not be null"); + Assert.notNull(path, "path must not be null"); + + this.context = context; + this.path = path; + + this.rootType = null; + } + + DefaultAggregatePath(RelationalMappingContext context, RelationalPersistentEntity rootType) { + + Assert.notNull(context, "context must not be null"); + Assert.notNull(rootType, "rootType must not be null"); + + this.context = context; + this.rootType = rootType; + + this.path = null; + } + + public static boolean isWritable(@Nullable PersistentPropertyPath path) { + return path == null || path.getLeafProperty().isWritable() && isWritable(path.getParentPath()); + } + + @Override + public boolean isRoot() { + return path == null; + } + + /** + * Returns the path that has the same beginning but is one segment shorter than this path. + * + * @return the parent path. Guaranteed to be not {@literal null}. + * @throws IllegalStateException when called on an empty path. + */ + @Override + public AggregatePath getParentPath() { + + if (isRoot()) { + throw new IllegalStateException("The parent path of a root path is not defined."); + } + + if (path.getLength() == 1) { + return context.getAggregatePath(path.getLeafProperty().getOwner()); + } + + return context.getAggregatePath(path.getParentPath()); + } + + /** + * The {@link RelationalPersistentEntity} associated with the leaf of this path. + * + * @return Might return {@literal null} when called on a path that does not represent an entity. + */ + @Override + @Nullable + public RelationalPersistentEntity getLeafEntity() { + return isRoot() ? rootType : context.getPersistentEntity(getRequiredLeafProperty().getActualType()); + } + + /** + * The {@link RelationalPersistentEntity} associated with the leaf of this path or throw {@link IllegalStateException} + * if the leaf cannot be resolved. + * + * @return the required {@link RelationalPersistentEntity} associated with the leaf of this path. + * @throws IllegalStateException if the persistent entity cannot be resolved. + */ + @Override + public RelationalPersistentEntity getRequiredLeafEntity() { + + RelationalPersistentEntity entity = getLeafEntity(); + + if (entity == null) { + + throw new IllegalStateException( + String.format("Couldn't resolve leaf PersistentEntity for type %s", path.getLeafProperty().getActualType())); + } + + return entity; + } + + @Override + public RelationalPersistentProperty getRequiredIdProperty() { + return isRoot() ? rootType.getRequiredIdProperty() : getRequiredLeafEntity().getRequiredIdProperty(); + + } + + @Override + public int getLength() { + return isRoot() ? 0 : path.getLength(); + } + + @Override + public Iterator iterator() { + + return new Iterator<>() { + + AggregatePath current = DefaultAggregatePath.this; + + @Override + public boolean hasNext() { + return current != null; + } + + @Override + public AggregatePath next() { + AggregatePath current = this.current; + + if (!current.isRoot()) { + this.current = current.getParentPath(); + } else { + this.current = null; + } + + return current; + } + }; + } + + /** + * Returns {@literal true} exactly when the path is non-empty and the leaf property an embedded one. + * + * @return if the leaf property is embedded. + */ + @Override + public boolean isEmbedded() { + return !isRoot() && getRequiredLeafProperty().isEmbedded(); + } + + /** + * @return {@literal true} when this is an empty path or the path references an entity. + */ + @Override + public boolean isEntity() { + return isRoot() || getRequiredLeafProperty().isEntity(); + } + + @Override + public String toString() { + return "AggregatePath[" + + (rootType == null ? path.getBaseProperty().getOwner().getType().getName() : rootType.getName()) + "]" + + ((isRoot()) ? "/" : path.toDotPath()); + } + + @Override + public String toDotPath() { + return isRoot() ? "" : path.toDotPath(); + } + + /** + * Returns {@literal true} if there are multiple values for this path, i.e. if the path contains at least one element + * that is a collection and array or a map. + * + * @return {@literal true} if the path contains a multivalued element. + */ + @Override + public boolean isMultiValued() { + + if (isRoot()) { + return false; + } + + RelationalPersistentProperty property = getRequiredLeafProperty(); + + return property.isCollectionLike() // + || property.isQualified() // + || getParentPath().isMultiValued(); + } + + /** + * @return {@literal true} if the leaf property of this path is a {@link java.util.Map}. + * @see RelationalPersistentProperty#isMap() + */ + @Override + public boolean isMap() { + return !isRoot() && getRequiredLeafProperty().isMap(); + } + + /** + * @return {@literal true} when this is references a {@link java.util.List} or {@link java.util.Map}. + */ + @Override + public boolean isQualified() { + return !isRoot() && getRequiredLeafProperty().isQualified(); + } + + @Override + public RelationalPersistentProperty getRequiredLeafProperty() { + + if (isRoot()) { + throw new IllegalStateException("Root path does not have a leaf property"); + } + + return path.getLeafProperty(); + } + + @Override + public RelationalPersistentProperty getBaseProperty() { + + if (isRoot()) { + throw new IllegalStateException("Root path does not have a base property"); + } + + return path.getBaseProperty(); + } + + /** + * @return {@literal true} when this is references a {@link java.util.Collection} or an array. + */ + @Override + public boolean isCollectionLike() { + return !isRoot() && getRequiredLeafProperty().isCollectionLike(); + } + + /** + * @return whether the leaf end of the path is ordered, i.e. the data to populate must be ordered. + * @see RelationalPersistentProperty#isOrdered() + */ + @Override + public boolean isOrdered() { + return !isRoot() && getRequiredLeafProperty().isOrdered(); + } + + /** + * Creates a new path by extending the current path by the property passed as an argument. + * + * @param property must not be {@literal null}. + * @return Guaranteed to be not {@literal null}. + */ + @Override + public AggregatePath append(RelationalPersistentProperty property) { + + PersistentPropertyPath newPath = isRoot() // + ? context.getPersistentPropertyPath(property.getName(), rootType.getType()) // + : context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), + path.getBaseProperty().getOwner().getType()); + + return context.getAggregatePath(newPath); + } + + @Override + public PersistentPropertyPath getRequiredPersistentPropertyPath() { + + Assert.state(!isRoot(), "Required path is not present"); + + return path; + } + + @Override + public boolean equals(Object o) { + + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + DefaultAggregatePath that = (DefaultAggregatePath) o; + return Objects.equals(context, that.context) && Objects.equals(rootType, that.rootType) + && Objects.equals(path, that.path); + } + + @Override + public int hashCode() { + + return Objects.hash(context, rootType, path); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultNamingStrategy.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultNamingStrategy.java index 95898e80d9..b86fc3d02b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultNamingStrategy.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultNamingStrategy.java @@ -61,6 +61,14 @@ public String getReverseColumnName(PersistentPropertyPathExtension path) { return getColumnNameReferencing(leafEntity); } + @Override + public String getReverseColumnName(AggregatePath path) { + + RelationalPersistentEntity leafEntity = AggregatePathUtil.getIdDefiningParentPath(path).getRequiredLeafEntity(); + + return getColumnNameReferencing(leafEntity); + } + private String getColumnNameReferencing(RelationalPersistentEntity leafEntity) { if (foreignKeyNaming == ForeignKeyNaming.IGNORE_RENAMING) { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ForeignTableDetector.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ForeignTableDetector.java new file mode 100644 index 0000000000..d83abe6eb1 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ForeignTableDetector.java @@ -0,0 +1,88 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping; + +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.util.Assert; + +/** + * @author Mark Paluch + */ +public class ForeignTableDetector extends TableAccessor{ + + private final AggregatePath path; + + ForeignTableDetector(AggregatePath path) { + super(path); + this.path = path; + } + + public static ForeignTableDetector of(AggregatePath path) { + + Assert.notNull(path, "AggregatePath must not be null"); + + if (path.isRoot()) { + throw new IllegalStateException("Root path does not map to a single column"); + } + + if (path.isEmbedded()) { + throw new IllegalStateException(String.format("Embedded property %s does not map to a foreign table", path)); + } + + if (!path.isQualified()) { + throw new IllegalStateException(String.format("Property %s does not map to a foreign table", path)); + } + + return new ForeignTableDetector(path); + } + + /** + * The column name used for the list index or map key of the leaf property of this path. + * + * @throws IllegalStateException if the key column cannot be determined for the current path. + */ + public SqlIdentifier getQualifierColumn() { + + SqlIdentifier keyColumn = path.getRequiredLeafProperty().getKeyColumn(); + + if (keyColumn == null) { + throw new IllegalStateException("Cannot determine key column for %s".formatted(path)); + } + return keyColumn; + } + + /** + * The type of the qualifier column of the leaf property of this path or {@literal null} if this is not applicable. + * + * @return may be {@literal null}. + */ + public Class getQualifierColumnType() { + + RelationalPersistentProperty property = path.getRequiredLeafProperty(); + + return property.getQualifierColumnType(); + } + + /** + * The name of the column used to reference the id in the parent table. + * + * @throws IllegalStateException when called on an empty path. + */ + public SqlIdentifier getReverseColumnName() { + return path.getRequiredLeafProperty().getReverseColumnName(path); + } + +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/NamingStrategy.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/NamingStrategy.java index 8cbaadc47b..ed255d9ee6 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/NamingStrategy.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/NamingStrategy.java @@ -15,7 +15,6 @@ */ package org.springframework.data.relational.core.mapping; -import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.util.ParsingUtils; import org.springframework.util.Assert; @@ -88,8 +87,25 @@ default String getReverseColumnName(RelationalPersistentProperty property) { return property.getOwner().getTableName().getReference(); } + /** + * @deprecated use {@link #getReverseColumnName(AggregatePath)} instead. + */ + @Deprecated(since = "3.2", forRemoval = true) default String getReverseColumnName(PersistentPropertyPathExtension path) { - return getTableName(path.getIdDefiningParentPath().getRequiredLeafEntity().getType()); + return getReverseColumnName(path.getAggregatePath()); + } + + /** + * provides the name of the column referencing the parent entity. + * + * @param path the path for which the reverse column name should get determined. Must not be null. + * @return a column name. + * @since 3.2 + */ + default String getReverseColumnName(AggregatePath path) { + + AggregatePath idDefiningParentPath = AggregatePathUtil.getIdDefiningParentPath(path); + return getTableName(idDefiningParentPath.getRequiredLeafEntity().getType()); } /** @@ -104,4 +120,5 @@ default String getKeyColumn(RelationalPersistentProperty property) { return getReverseColumnName(property) + "_key"; } + } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java index 96358884fb..11277bd11c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java @@ -20,7 +20,6 @@ import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.util.Lazy; import org.springframework.lang.Nullable; @@ -35,7 +34,9 @@ * @author Daniil Razorenov * @author Kurt Niemi * @since 1.1 + * @deprecated use {@link AggregatePath} instead */ +@Deprecated(since = "3.2", forRemoval = true) public class PersistentPropertyPathExtension { private final RelationalPersistentEntity entity; @@ -155,8 +156,8 @@ public RelationalPersistentEntity getRequiredLeafEntity() { if (this.path == null) { throw new IllegalStateException("Couldn't resolve leaf PersistentEntity absent path"); } - throw new IllegalStateException(String.format("Couldn't resolve leaf PersistentEntity for type %s", - path.getLeafProperty().getActualType())); + throw new IllegalStateException( + String.format("Couldn't resolve leaf PersistentEntity for type %s", path.getLeafProperty().getActualType())); } return entity; @@ -387,14 +388,6 @@ public Class getActualType() { : path.getLeafProperty().getActualType(); } - /** - * @return whether the leaf end of the path is ordered, i.e. the data to populate must be ordered. - * @see RelationalPersistentProperty#isOrdered() - */ - public boolean isOrdered() { - return path != null && path.getLeafProperty().isOrdered(); - } - /** * @return {@literal true} if the leaf property of this path is a {@link java.util.Map}. * @see RelationalPersistentProperty#isMap() @@ -481,8 +474,7 @@ private SqlIdentifier assembleColumnName(SqlIdentifier suffix) { private SqlIdentifier prefixWithTableAlias(SqlIdentifier columnName) { SqlIdentifier tableAlias = getTableAlias(); - return tableAlias == null ? columnName - : columnName.transform(name -> tableAlias.getReference() + "_" + name); + return tableAlias == null ? columnName : columnName.transform(name -> tableAlias.getReference() + "_" + name); } @Override @@ -500,4 +492,13 @@ public boolean equals(@Nullable Object o) { public int hashCode() { return Objects.hash(entity, path); } + + public AggregatePath getAggregatePath() { + if (path != null) { + + return ((RelationalMappingContext) context).getAggregatePath(path); + } else { + return ((RelationalMappingContext) context).getAggregatePath(entity); + } + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java index 1c70375cc3..9bfad5d904 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java @@ -15,8 +15,12 @@ */ package org.springframework.data.relational.core.mapping; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; +import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.context.AbstractMappingContext; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.Property; @@ -39,6 +43,8 @@ public class RelationalMappingContext extends AbstractMappingContext, RelationalPersistentProperty> { private final NamingStrategy namingStrategy; + private final Map aggregatePathCache = new ConcurrentHashMap<>(); + private boolean forceQuote = true; private final ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(EvaluationContextProvider.DEFAULT); @@ -129,4 +135,18 @@ protected void applyDefaults(BasicRelationalPersistentProperty persistentPropert persistentProperty.setExpressionEvaluator(this.expressionEvaluator); } + /** + * Provides an {@link AggregatePath} for the provided {@link PersistentPropertyPath}. + * + * @param path the path to provide an {@link AggregatePath} for. Must not be null. + * @return an {@link AggregatePath} on the provided path. + * @since 3.2 + */ + public AggregatePath getAggregatePath(PersistentPropertyPath path) { + return aggregatePathCache.computeIfAbsent(path, key -> new DefaultAggregatePath(this, path)); + } + + public AggregatePath getAggregatePath(RelationalPersistentEntity type) { + return aggregatePathCache.computeIfAbsent(type, key -> new DefaultAggregatePath(this, type)); + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java index 021be1646e..9d2fbc16c8 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java @@ -38,8 +38,21 @@ public interface RelationalPersistentProperty extends PersistentProperty getOwner(); + /** + * @deprecated use {@link #getReverseColumnName(AggregatePath)} instead + */ + @Deprecated(since = "3.2", forRemoval = true) SqlIdentifier getReverseColumnName(PersistentPropertyPathExtension path); + + // TODO: Remove this method as we have a cycle between path and property + /** + * @param path + * @return + * @since 3.2 + */ + SqlIdentifier getReverseColumnName(AggregatePath path); + @Nullable SqlIdentifier getKeyColumn(); @@ -78,7 +91,7 @@ default String getEmbeddedPrefix() { /** * Returns whether this property is only to be used during inserts and read. - * + * * @since 3.0 */ boolean isInsertOnly(); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/SingleColumnAggregatePath.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/SingleColumnAggregatePath.java new file mode 100644 index 0000000000..842bac6006 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/SingleColumnAggregatePath.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping; + +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.util.Assert; + +/** + * @author Mark Paluch + */ +public class SingleColumnAggregatePath { + + private final AggregatePath path; + + private SingleColumnAggregatePath(AggregatePath path) { + this.path = path; + } + + public static SingleColumnAggregatePath of(AggregatePath path) { + + Assert.notNull(path, "AggregatePath must not be null"); + + if (path.isRoot()) { + throw new IllegalStateException("Root path does not map to a single column"); + } + + if (path.isEmbedded()) { + throw new IllegalStateException(String.format("Embedded property %s does not map to a single column", path)); + } + + return new SingleColumnAggregatePath(path); + } + + /** + * The name of the column used to reference the id in the parent table. + * + * @throws IllegalStateException when called on an empty path. + */ + public SqlIdentifier getReverseColumnName() { + return path.getRequiredLeafProperty().getReverseColumnName(path); + } + +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/TableAccessor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/TableAccessor.java new file mode 100644 index 0000000000..95a94bdfe1 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/TableAccessor.java @@ -0,0 +1,140 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping; + +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * @author Mark Paluch + */ +public class TableAccessor { + + private final AggregatePath path; + + TableAccessor(AggregatePath path) { + this.path = path; + } + + public static TableAccessor of(AggregatePath path) { + return new TableAccessor(path); + } + + /** + * The alias used for the table on which this path is based. + * + * @return a table alias, {@literal null} if the table owning path is the empty path. + */ + public boolean hasTableAlias() { + + TableAccessor tableOwner = getTableOwner(); + AggregatePath path = tableOwner.getPath(); + + if (path.isRoot()) { + return false; + } + + // TODO: Do this better + return assembleTableAlias(path) != null; + } + + /** + * The fully qualified name of the table this path is tied to or of the longest ancestor path that is actually tied to + * a table. + * + * @return the name of the table. Guaranteed to be not {@literal null}. + * @since 3.0 + */ + public SqlIdentifier getQualifiedTableName() { + return getTableOwner().getPath().getRequiredLeafEntity().getQualifiedTableName(); + } + + /** + * The alias used for the table on which this path is based. + * + * @return a table alias, {@literal null} if the table owning path is the empty path. + */ + @Nullable + public SqlIdentifier findTableAlias() { + + // TODO: Make non-nullable + TableAccessor tableOwner = getTableOwner(); + AggregatePath path = tableOwner.getPath(); + + return path.isRoot() ? null : assembleTableAlias(path); + } + + public TableAccessor getTableOwner() { + + AggregatePath result = path.filter(it -> it.isEntity() && !it.isEmbedded()); + + if (result == null) { + throw new IllegalArgumentException("Cannot find table-owning AggregatePath for %s".formatted(path)); + } + + if (result == path) { + return this; + } + + return createTableAccessor(result); + } + + TableAccessor createTableAccessor(AggregatePath path) { + return new TableAccessor(path); + } + + public AggregatePath getPath() { + return path; + } + + SqlIdentifier prefixWithTableAlias(SqlIdentifier columnName) { + + SqlIdentifier tableAlias = findTableAlias(); + return tableAlias == null ? columnName : columnName.transform(name -> tableAlias.getReference() + "_" + name); + } + + @Nullable + private static SqlIdentifier assembleTableAlias(AggregatePath path) { + + RelationalPersistentProperty leafProperty = path.getRequiredLeafProperty(); + String prefix; + if (path.isEmbedded()) { + prefix = leafProperty.getEmbeddedPrefix(); + + } else { + prefix = leafProperty.getName(); + } + + if (path.getLength() == 1) { + Assert.notNull(prefix, "Prefix mus not be null"); + return StringUtils.hasText(prefix) ? SqlIdentifier.quoted(prefix) : null; + } + + AggregatePath parentPath = path.getParentPath(); + SqlIdentifier sqlIdentifier = assembleTableAlias(parentPath); + + if (sqlIdentifier != null) { + + return parentPath.isEmbedded() ? sqlIdentifier.transform(name -> name.concat(prefix)) + : sqlIdentifier.transform(name -> name + "_" + prefix); + } + + return SqlIdentifier.quoted(prefix); + } + +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/AggregatePathUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/AggregatePathUnitTests.java new file mode 100644 index 0000000000..b852e9897d --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/AggregatePathUnitTests.java @@ -0,0 +1,497 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.relational.core.mapping; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.SoftAssertions.*; +import static org.springframework.data.relational.core.sql.SqlIdentifier.*; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.ReadOnlyProperty; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.data.util.Streamable; + +/** + * Tests for {@link AggregatePath}. + * + * @author Jens Schauder + * @author Mark Paluch + */ +class AggregatePathUnitTests { + RelationalMappingContext context = new RelationalMappingContext(); + + private RelationalPersistentEntity entity = context.getRequiredPersistentEntity(DummyEntity.class); + + @Test // GH-1525 + void isNotRootForNonRootPath() { + + AggregatePath path = context.getAggregatePath(context.getPersistentPropertyPath("entityId", DummyEntity.class)); + + assertThat(path.isRoot()).isFalse(); + } + + @Test // GH-1525 + void isRootForRootPath() { + + AggregatePath path = context.getAggregatePath(entity); + + assertThat(path.isRoot()).isTrue(); + } + + @Test // GH-1525 + void getParentPath() { + + assertSoftly(softly -> { + + softly.assertThat(path("second.third2.value").getParentPath()).isEqualTo(path("second.third2")); + softly.assertThat(path("second.third2").getParentPath()).isEqualTo(path("second")); + softly.assertThat(path("second").getParentPath()).isEqualTo(path()); + + softly.assertThatThrownBy(() -> path().getParentPath()).isInstanceOf(IllegalStateException.class); + }); + } + + @Test // GH-1525 + void getRequiredLeafEntity() { + + assertSoftly(softly -> { + + softly.assertThat(path().getRequiredLeafEntity()).isEqualTo(entity); + softly.assertThat(path("second").getRequiredLeafEntity()) + .isEqualTo(context.getRequiredPersistentEntity(Second.class)); + softly.assertThat(path("second.third").getRequiredLeafEntity()) + .isEqualTo(context.getRequiredPersistentEntity(Third.class)); + softly.assertThat(path("secondList").getRequiredLeafEntity()) + .isEqualTo(context.getRequiredPersistentEntity(Second.class)); + + softly.assertThatThrownBy(() -> path("secondList.third.value").getRequiredLeafEntity()) + .isInstanceOf(IllegalStateException.class); + + }); + } + + @Test // GH-1525 + void idDefiningPath() { + + assertSoftly(softly -> { + + softly.assertThat(AggregatePathUtil.getIdDefiningParentPath(path("second.third2.value"))).isEqualTo(path()); + softly.assertThat(AggregatePathUtil.getIdDefiningParentPath(path("second.third.value"))).isEqualTo(path()); + softly.assertThat(AggregatePathUtil.getIdDefiningParentPath(path("secondList.third2.value"))).isEqualTo(path()); + softly.assertThat(AggregatePathUtil.getIdDefiningParentPath(path("secondList.third.value"))).isEqualTo(path()); + softly.assertThat(AggregatePathUtil.getIdDefiningParentPath(path("second2.third2.value"))).isEqualTo(path()); + softly.assertThat(AggregatePathUtil.getIdDefiningParentPath(path("second2.third.value"))).isEqualTo(path()); + softly.assertThat(AggregatePathUtil.getIdDefiningParentPath(path("withId.second.third2.value"))).isEqualTo(path("withId")); + softly.assertThat(AggregatePathUtil.getIdDefiningParentPath(path("withId.second.third.value"))).isEqualTo(path("withId")); + }); + } + + @Test // GH-1525 + void getRequiredIdProperty() { + + assertSoftly(softly -> { + + softly.assertThat(path().getRequiredIdProperty().getName()).isEqualTo("entityId"); + softly.assertThat(path("withId").getRequiredIdProperty().getName()).isEqualTo("withIdId"); + softly.assertThatThrownBy(() -> path("second").getRequiredIdProperty()).isInstanceOf(IllegalStateException.class); + }); + } + + @Test // GH-1525 + void reverseColumnName() { + + assertSoftly(softly -> { + + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("second.third2"))).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("second.third"))).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("secondList.third2"))).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("secondList.third"))).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("second2.third2"))).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("second2.third"))).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("withId.second.third2.value"))).isEqualTo(quoted("WITH_ID")); + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("withId.second.third"))).isEqualTo(quoted("WITH_ID")); + softly.assertThat(AggregatePathUtil.getReverseColumnName(path("withId.second2.third"))).isEqualTo(quoted("WITH_ID")); + }); + } + + @Test // GH-1525 + void getQualifierColumn() { + + assertSoftly(softly -> { + + softly.assertThat(ForeignTableDetector.of(path("secondList")).getQualifierColumn()).isEqualTo(SqlIdentifier.quoted("DUMMY_ENTITY_KEY")); + }); + } + + @Test // GH-1525 + void getQualifierColumnType() { + + assertSoftly(softly -> { + + softly.assertThat(ForeignTableDetector.of(path("secondList")).getQualifierColumnType()).isEqualTo(Integer.class); + }); + } + + @Test // GH-1525 + void extendBy() { + + assertSoftly(softly -> { + + softly.assertThat(path().append(entity.getRequiredPersistentProperty("withId"))).isEqualTo(path("withId")); + softly.assertThat(path("withId").append(path("withId").getRequiredIdProperty())) + .isEqualTo(path("withId.withIdId")); + }); + } + + @Test // GH-1525 + void isWritable() { + + assertSoftly(softly -> { + softly.assertThat(AggregatePathUtil.isWritable(createSimplePath("withId"))).describedAs("simple path is writable") + .isTrue(); + softly.assertThat(AggregatePathUtil.isWritable(createSimplePath("secondList.third2"))) + .describedAs("long path is writable").isTrue(); + softly.assertThat(AggregatePathUtil.isWritable(createSimplePath("second"))) + .describedAs("simple read only path is not writable").isFalse(); + softly.assertThat(AggregatePathUtil.isWritable(createSimplePath("second.third"))) + .describedAs("long path containing read only element is not writable").isFalse(); + }); + } + + @Test // GH-1525 + void isEmbedded() { + + assertSoftly(softly -> { + softly.assertThat(path().isEmbedded()).isFalse(); + softly.assertThat(path("withId").isEmbedded()).isFalse(); + softly.assertThat(path("second2.third").isEmbedded()).isFalse(); + softly.assertThat(path("second2").isEmbedded()).isTrue(); + + }); + } + + + @Test // GH-1525 + void isEntity() { + + assertSoftly(softly -> { + + softly.assertThat(path().isEntity()).isTrue(); + softly.assertThat(path("second").isEntity()).isTrue(); + softly.assertThat(path("second.third2").isEntity()).isTrue(); + softly.assertThat(path("secondList.third2").isEntity()).isTrue(); + softly.assertThat(path("secondList").isEntity()).isTrue(); + softly.assertThat(path("second.third2.value").isEntity()).isFalse(); + softly.assertThat(path("secondList.third2.value").isEntity()).isFalse(); + }); + } + + @Test // GH-1525 + void isMultiValued() { + + assertSoftly(softly -> { + + softly.assertThat(path().isMultiValued()).isFalse(); + softly.assertThat(path("second").isMultiValued()).isFalse(); + softly.assertThat(path("second.third2").isMultiValued()).isFalse(); + softly.assertThat(path("secondList.third2").isMultiValued()).isTrue(); + softly.assertThat(path("secondList").isMultiValued()).isTrue(); + }); + } + + @Test // GH-1525 + void isQualified() { + + assertSoftly(softly -> { + + softly.assertThat(path().isQualified()).isFalse(); + softly.assertThat(path("second").isQualified()).isFalse(); + softly.assertThat(path("second.third2").isQualified()).isFalse(); + softly.assertThat(path("secondList.third2").isQualified()).isFalse(); + softly.assertThat(path("secondList").isQualified()).isTrue(); + }); + } + + @Test // GH-1525 + void isMap() { + + assertSoftly(softly -> { + + softly.assertThat(path().isMap()).isFalse(); + softly.assertThat(path("second").isMap()).isFalse(); + softly.assertThat(path("second.third2").isMap()).isFalse(); + softly.assertThat(path("secondList.third2").isMap()).isFalse(); + softly.assertThat(path("secondList").isMap()).isFalse(); + softly.assertThat(path("secondMap.third2").isMap()).isFalse(); + softly.assertThat(path("secondMap").isMap()).isTrue(); + }); + } + + @Test // GH-1525 + void isCollectionLike() { + + assertSoftly(softly -> { + + softly.assertThat(path().isCollectionLike()).isFalse(); + softly.assertThat(path("second").isCollectionLike()).isFalse(); + softly.assertThat(path("second.third2").isCollectionLike()).isFalse(); + softly.assertThat(path("secondList.third2").isCollectionLike()).isFalse(); + softly.assertThat(path("secondMap.third2").isCollectionLike()).isFalse(); + softly.assertThat(path("secondMap").isCollectionLike()).isFalse(); + softly.assertThat(path("secondList").isCollectionLike()).isTrue(); + }); + } + + @Test // GH-1525 + void isOrdered() { + + assertSoftly(softly -> { + + softly.assertThat(path().isOrdered()).isFalse(); + softly.assertThat(path("second").isOrdered()).isFalse(); + softly.assertThat(path("second.third2").isOrdered()).isFalse(); + softly.assertThat(path("secondList.third2").isOrdered()).isFalse(); + softly.assertThat(path("secondMap.third2").isOrdered()).isFalse(); + softly.assertThat(path("secondMap").isOrdered()).isFalse(); + softly.assertThat(path("secondList").isOrdered()).isTrue(); + }); + } + + @Test // GH-1525 + void getTableAlias() { + + assertSoftly(softly -> { + + softly.assertThat(TableAccessor.of(path()).findTableAlias()).isEqualTo(null); + softly.assertThat(TableAccessor.of(path("second")).findTableAlias()).isEqualTo(quoted("second")); + softly.assertThat(TableAccessor.of(path("second.third2")).findTableAlias()).isEqualTo(quoted("second")); + softly.assertThat(TableAccessor.of(path("second.third2.value")).findTableAlias()).isEqualTo(quoted("second")); + softly.assertThat(TableAccessor.of(path("second.third")).findTableAlias()).isEqualTo(quoted("second_third")); + softly.assertThat(TableAccessor.of(path("second.third.value")).findTableAlias()).isEqualTo(quoted("second_third")); + softly.assertThat(TableAccessor.of(path("secondList.third2")).findTableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(TableAccessor.of(path("secondList.third2.value")).findTableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(TableAccessor.of(path("secondList.third")).findTableAlias()).isEqualTo(quoted("secondList_third")); + softly.assertThat(TableAccessor.of(path("secondList.third.value")).findTableAlias()).isEqualTo(quoted("secondList_third")); + softly.assertThat(TableAccessor.of(path("secondList")).findTableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(TableAccessor.of(path("second2.third")).findTableAlias()).isEqualTo(quoted("secthird")); + softly.assertThat(TableAccessor.of(path("second3.third")).findTableAlias()).isEqualTo(quoted("third")); + }); + } + @Test // GH-1525 + void getTableName() { + + assertSoftly(softly -> { + + softly.assertThat(TableAccessor.of(path()).getQualifiedTableName()).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(TableAccessor.of(path("second")).getQualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(TableAccessor.of(path("second.third2")).getQualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(TableAccessor.of(path("second.third2.value")).getQualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(TableAccessor.of(path("secondList.third2")).getQualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(TableAccessor.of(path("secondList.third2.value")).getQualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(TableAccessor.of(path("secondList")).getQualifiedTableName()).isEqualTo(quoted("SECOND")); + }); + } + @Test // GH-1525 + void getColumnName() { + + assertSoftly(softly -> { + + softly.assertThat(ColumnDetector.of(path("second.third2.value")).getColumnName()).isEqualTo(quoted("THRDVALUE")); + softly.assertThat(ColumnDetector.of(path("second.third.value")).getColumnName()).isEqualTo(quoted("VALUE")); + softly.assertThat(ColumnDetector.of(path("secondList.third2.value")).getColumnName()).isEqualTo(quoted("THRDVALUE")); + softly.assertThat(ColumnDetector.of(path("secondList.third.value")).getColumnName()).isEqualTo(quoted("VALUE")); + softly.assertThat(ColumnDetector.of(path("second2.third2.value")).getColumnName()).isEqualTo(quoted("SECTHRDVALUE")); + softly.assertThat(ColumnDetector.of(path("second2.third.value")).getColumnName()).isEqualTo(quoted("VALUE")); + }); + } + + @Test // GH-1525 + void getColumnAlias() { + + assertSoftly(softly -> { + + softly.assertThat(ColumnDetector.of(path("second.third2.value")).getColumnAlias()).isEqualTo(quoted("SECOND_THRDVALUE")); + softly.assertThat(ColumnDetector.of(path("second.third.value")).getColumnAlias()).isEqualTo(quoted("SECOND_THIRD_VALUE")); + softly.assertThat(ColumnDetector.of(path("secondList.third2.value")).getColumnAlias()).isEqualTo(quoted("SECONDLIST_THRDVALUE")); + softly.assertThat(ColumnDetector.of(path("secondList.third.value")).getColumnAlias()).isEqualTo(quoted("SECONDLIST_THIRD_VALUE")); + softly.assertThat(ColumnDetector.of(path("second2.third2.value")).getColumnAlias()).isEqualTo(quoted("SECTHRDVALUE")); + softly.assertThat(ColumnDetector.of(path("second2.third.value")).getColumnAlias()).isEqualTo(quoted("SECTHIRD_VALUE")); + }); + } + + @Test // GH-1525 + void getReverseColumnAlias() { + + assertSoftly(softly -> { + + softly.assertThat(AggregatePathUtil.getReverseColumnNameAlias(path("second.third2.value"))).isEqualTo(quoted("SECOND_DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnNameAlias(path("second.third.value"))).isEqualTo(quoted("SECOND_THIRD_DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnNameAlias(path("secondList.third2.value"))).isEqualTo(quoted("SECONDLIST_DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnNameAlias(path("secondList.third.value"))).isEqualTo(quoted("SECONDLIST_THIRD_DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnNameAlias(path("second2.third2.value"))).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getReverseColumnNameAlias(path("second2.third.value"))).isEqualTo(quoted("SECTHIRD_DUMMY_ENTITY")); + }); + } + + @Test // GH-1525 + void getRequiredLeafProperty() { + + assertSoftly(softly -> { + + softly.assertThat(path("second.third2.value").getRequiredLeafProperty()).isEqualTo(context.getRequiredPersistentEntity(Third.class).getPersistentProperty("value")); + softly.assertThat(path("second.third").getRequiredLeafProperty()).isEqualTo(context.getRequiredPersistentEntity(Second.class).getPersistentProperty("third")); + softly.assertThat(path("secondList").getRequiredLeafProperty()).isEqualTo(entity.getPersistentProperty("secondList")); + softly.assertThatThrownBy(() -> path().getRequiredLeafProperty()).isInstanceOf(IllegalStateException.class); + }); + } + + @Test // GH-1525 + void getBaseProperty() { + + assertSoftly(softly -> { + + softly.assertThat(path("second.third2.value").getBaseProperty()).isEqualTo(entity.getPersistentProperty("second")); + softly.assertThat(path("second.third.value").getBaseProperty()).isEqualTo(entity.getPersistentProperty("second")); + softly.assertThat(path("secondList.third2.value").getBaseProperty()).isEqualTo(entity.getPersistentProperty("secondList")); + softly.assertThatThrownBy(() -> path().getBaseProperty()).isInstanceOf(IllegalStateException.class); + }); + } + + + @Test // GH-1525 + void getIdColumnName() { + + assertSoftly(softly -> { + + softly.assertThat(ColumnDetector.of(path()).getIdColumnName()).isEqualTo(quoted("ENTITY_ID")); + softly.assertThat(ColumnDetector.of(path("withId")).getIdColumnName()).isEqualTo(quoted("WITH_ID_ID")); + + softly.assertThatThrownBy(() -> ColumnDetector.of(path("second")).getIdColumnName()).isInstanceOf(IllegalStateException.class); + softly.assertThatThrownBy(() ->ColumnDetector.of(path("second.third2")).getIdColumnName()).isInstanceOf(IllegalStateException.class); + softly.assertThatThrownBy(() ->ColumnDetector.of(path("withId.second")).getIdColumnName()).isInstanceOf(IllegalStateException.class); + }); + } + + @Test // GH-1525 + void toDotPath() { + + assertSoftly(softly -> { + + softly.assertThat(path().toDotPath()).isEqualTo(""); + softly.assertThat(path("second.third.value").toDotPath()).isEqualTo("second.third.value"); + }); + } + + @Test // GH-1525 + void getRequiredPersistentPropertyPath() { + + assertSoftly(softly -> { + + softly.assertThat(path("second.third.value").getRequiredPersistentPropertyPath()).isEqualTo(createSimplePath("second.third.value")); + softly.assertThatThrownBy(() -> path().getRequiredPersistentPropertyPath()).isInstanceOf(IllegalStateException.class); + }); + } + + @Test // GH-1525 + void getEffectiveIdColumnName() { + + assertSoftly(softly -> { + + softly.assertThat(AggregatePathUtil.getEffectiveIdColumnName(path())).isEqualTo(quoted("ENTITY_ID")); + softly.assertThat(AggregatePathUtil.getEffectiveIdColumnName(path("second.third2"))).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(AggregatePathUtil.getEffectiveIdColumnName(path("withId.second.third"))).isEqualTo(quoted("WITH_ID")); + softly.assertThat(AggregatePathUtil.getEffectiveIdColumnName(path("withId.second.third2.value"))).isEqualTo(quoted("WITH_ID")); + }); + } + + @Test // GH-1525 + void getLength() { + + assertSoftly(softly -> { + + softly.assertThat(path().getLength()).isEqualTo(0); + softly.assertThat(path().isRoot()).isTrue(); + softly.assertThat(path("second.third2").getLength()).isEqualTo(2); + softly.assertThat(path("withId.second.third").getLength()).isEqualTo(3); + softly.assertThat(path("withId.second.third2.value").getLength()).isEqualTo(4); + }); + } + + @Test // GH-1525 + void shouldIteratePath() { + + AggregatePath path = path("withId.second.third2"); + List aggregatePaths = Streamable.of(path::iterator).stream().toList(); + + assertThat(aggregatePaths).hasSize(4).containsExactly(path, path.getParentPath(), path.getParentPath().getParentPath(), path.getParentPath().getParentPath().getParentPath()); + } + + @Test // GH-1525 + void shouldFilterPath() { + + assertThat(path("withId.second.third2").filter(AggregatePath::isRoot)).isEqualTo(path()); + assertThat(path("withId.second.third2").filter(path -> true)).isEqualTo(path("withId.second.third2")); + assertThat(path("withId.second.third2").filter(path -> false)).isNull(); + } + + private AggregatePath path() { + return context.getAggregatePath(entity); + } + + private AggregatePath path(String path) { + return context.getAggregatePath(createSimplePath(path)); + } + + PersistentPropertyPath createSimplePath(String path) { + return PersistentPropertyPathTestUtils.getPath(context, path, DummyEntity.class); + } + + @SuppressWarnings("unused") + static class DummyEntity { + @Id Long entityId; + @ReadOnlyProperty Second second; + @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL, prefix = "sec") Second second2; + @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL) Second second3; + List secondList; + Map secondMap; + WithId withId; + } + + @SuppressWarnings("unused") + static class Second { + Third third; + @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL, prefix = "thrd") Third third2; + } + + @SuppressWarnings("unused") + static class Third { + String value; + } + + @SuppressWarnings("unused") + static class WithId { + @Id Long withIdId; + Second second; + @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL, prefix = "sec") Second second2; + } + +} diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java index 1ecb663dc3..26357198a1 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java @@ -64,7 +64,7 @@ public void detectsAnnotatedColumnAndKeyName() { .findPersistentPropertyPaths(DummyEntity.class, p -> p.getName().equals("someList")).getFirst() .orElseThrow(() -> new AssertionFailedError("Couldn't find path for 'someList'")); - assertThat(listProperty.getReverseColumnName(new PersistentPropertyPathExtension(context, path))) + assertThat(listProperty.getReverseColumnName(context.getAggregatePath(path))) .isEqualTo(quoted("dummy_column_name")); assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("dummy_key_column_name")); } @@ -89,7 +89,6 @@ void shouldEvaluateMappedCollectionExpressions() { RelationalPersistentProperty property = entity.getRequiredPersistentProperty("someList"); assertThat(property.getKeyColumn()).isEqualTo(quoted("key_col")); - assertThat(property.getReverseColumnName(null)).isEqualTo(quoted("id_col")); } @Test // DATAJDBC-111 diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/PersistentPropertyPathTestUtils.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathTestUtils.java similarity index 74% rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/PersistentPropertyPathTestUtils.java rename to spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathTestUtils.java index ebcf54a931..2d47917866 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/PersistentPropertyPathTestUtils.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathTestUtils.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.jdbc.core.mapping; +package org.springframework.data.relational.core.mapping; import lombok.experimental.UtilityClass; import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -30,10 +31,14 @@ public class PersistentPropertyPathTestUtils { public static PersistentPropertyPath getPath(RelationalMappingContext context, String path, Class baseType) { - return context.findPersistentPropertyPaths(baseType, p -> p.isEntity()) // - .filter(p -> p.toDotPath().equals(path)) // - .stream() // - .findFirst() // - .orElseThrow(() -> new IllegalArgumentException(String.format("No path for %s based on %s", path, baseType))); + PersistentPropertyPaths persistentPropertyPaths = context + .findPersistentPropertyPaths(baseType, p -> true); + + return persistentPropertyPaths + .filter(p -> p.toDotPath().equals(path)) + .stream() + .findFirst() + .orElse(null); + } } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalMappingContextUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalMappingContextUnitTests.java index 1d3d0e96ab..c76b3a80b4 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalMappingContextUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalMappingContextUnitTests.java @@ -21,8 +21,10 @@ import java.util.HashSet; import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; +import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.model.SimpleTypeHolder; /** @@ -31,21 +33,60 @@ * @author Toshiaki Maki */ public class RelationalMappingContextUnitTests { + RelationalMappingContext context = new RelationalMappingContext(); + SimpleTypeHolder holder = new SimpleTypeHolder(new HashSet<>(Arrays.asList(UUID.class)), true); + + @BeforeEach + void setup() { + context.setSimpleTypeHolder(holder); + } @Test // DATAJDBC-229 public void uuidPropertyIsNotEntity() { - SimpleTypeHolder holder = new SimpleTypeHolder(new HashSet<>(Arrays.asList(UUID.class)), true); - - RelationalMappingContext mappingContext = new RelationalMappingContext(); - mappingContext.setSimpleTypeHolder(holder); - - RelationalPersistentEntity entity = mappingContext.getPersistentEntity(EntityWithUuid.class); + RelationalPersistentEntity entity = context.getPersistentEntity(EntityWithUuid.class); RelationalPersistentProperty uuidProperty = entity.getRequiredPersistentProperty("uuid"); assertThat(uuidProperty.isEntity()).isFalse(); } + @Test // GH-1525 + public void canObtainAggregatePath() { + + PersistentPropertyPath path = context.getPersistentPropertyPath("uuid", + EntityWithUuid.class); + AggregatePath aggregatePath = context.getAggregatePath(path); + + assertThat(aggregatePath).isNotNull(); + } + + @Test // GH-1525 + public void innerAggregatePathsGetCached() { + + context = new RelationalMappingContext(); + context.setSimpleTypeHolder(holder); + + PersistentPropertyPath path = context.getPersistentPropertyPath("uuid", + EntityWithUuid.class); + + AggregatePath one = context.getAggregatePath(path); + AggregatePath two = context.getAggregatePath(path); + + assertThat(one).isSameAs(two); + } + + @Test // GH-1525 + public void rootAggregatePathsGetCached() { + + context = new RelationalMappingContext(); + context.setSimpleTypeHolder(holder); + + AggregatePath one = context.getAggregatePath(context.getRequiredPersistentEntity(EntityWithUuid.class)); + AggregatePath two = context.getAggregatePath(context.getRequiredPersistentEntity(EntityWithUuid.class)); + + assertThat(one).isSameAs(two); + } + static class EntityWithUuid { @Id UUID uuid; }