From d00e928db52b7bdecbc4b735a3aeadf73ca767ad Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Wed, 17 May 2023 14:43:12 +0200 Subject: [PATCH] Introduce AggregatePath. AggregatePath replaces PersistentPropertyPathExtension. It gets created and cached by the RelationalMappingContext, which should be more efficient and certainly looks nicer. Closes #1525 Original pull request #1486 --- .../JdbcAggregateChangeExecutionContext.java | 33 +- .../jdbc/core/convert/BasicJdbcConverter.java | 35 +- .../convert/DefaultDataAccessStrategy.java | 18 +- .../jdbc/core/convert/EntityRowMapper.java | 18 +- ...dbcBackReferencePropertyValueProvider.java | 11 +- .../data/jdbc/core/convert/JdbcConverter.java | 24 +- .../core/convert/JdbcIdentifierBuilder.java | 30 +- .../convert/JdbcPropertyValueProvider.java | 13 +- .../jdbc/core/convert/MapEntityRowMapper.java | 6 +- .../data/jdbc/core/convert/SqlContext.java | 18 +- .../data/jdbc/core/convert/SqlGenerator.java | 105 ++-- .../repository/query/JdbcQueryCreator.java | 51 +- .../jdbc/repository/query/SqlContext.java | 19 +- ...angeExecutorContextImmutableUnitTests.java | 8 +- ...gregateChangeExecutorContextUnitTests.java | 8 +- ...sistentPropertyPathExtensionUnitTests.java | 11 +- ...a => PersistentPropertyPathTestUtils.java} | 14 +- .../JdbcIdentifierBuilderUnitTests.java | 12 +- ...orContextBasedNamingStrategyUnitTests.java | 4 +- .../SqlGeneratorEmbeddedUnitTests.java | 12 +- ...GeneratorFixedNamingStrategyUnitTests.java | 16 +- .../core/convert/SqlGeneratorUnitTests.java | 50 +- .../BasicJdbcPersistentPropertyUnitTests.java | 55 +- .../model/DefaultNamingStrategyUnitTests.java | 2 +- .../MyBatisDataAccessStrategyUnitTests.java | 4 +- .../r2dbc/convert/MappingR2dbcConverter.java | 5 +- .../conversion/BasicRelationalConverter.java | 20 +- .../RelationalEntityDeleteWriter.java | 3 +- .../core/conversion/WritingContext.java | 3 +- .../core/mapping/AggregatePath.java | 342 ++++++++++++ .../core/mapping/AggregatePathTableUtils.java | 94 ++++ .../core/mapping/AggregatePathTraversal.java | 49 ++ .../BasicRelationalPersistentProperty.java | 7 + .../core/mapping/CachingNamingStrategy.java | 5 + .../core/mapping/DefaultAggregatePath.java | 255 +++++++++ .../core/mapping/DefaultNamingStrategy.java | 11 +- .../core/mapping/NamingStrategy.java | 12 +- .../PersistentPropertyPathExtension.java | 29 +- .../mapping/RelationalMappingContext.java | 37 ++ .../mapping/RelationalPersistentProperty.java | 14 +- ...RelationalPersistentPropertyUnitTests.java | 18 +- .../DefaultAggregatePathUnitTests.java | 522 ++++++++++++++++++ .../PersistentPropertyPathTestUtils.java | 17 +- .../RelationalMappingContextUnitTests.java | 53 +- 44 files changed, 1784 insertions(+), 289 deletions(-) rename spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/{PropertyPathTestingUtils.java => PersistentPropertyPathTestUtils.java} (81%) create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathTableUtils.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathTraversal.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java create mode 100644 spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/DefaultAggregatePathUnitTests.java rename {spring-data-jdbc/src/test/java/org/springframework/data/jdbc => spring-data-relational/src/test/java/org/springframework/data/relational}/core/mapping/PersistentPropertyPathTestUtils.java (75%) 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..ed67dfdfb4 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,12 @@ 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.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 +57,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 +176,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 +188,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, context.getAggregatePath(action.getPropertyPath()).getIdDefiningParentPath()); 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..31ab36f2f2 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 @@ -47,7 +47,8 @@ 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.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.IdentifierProcessing; @@ -86,14 +87,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 +117,7 @@ public BasicJdbcConverter( * @since 2.0 */ public BasicJdbcConverter( - MappingContext, ? extends RelationalPersistentProperty> context, + RelationalMappingContext context, RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory, IdentifierProcessing identifierProcessing) { @@ -300,12 +301,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 +352,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 +362,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 +370,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 +378,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 +396,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 +456,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(rootPath.getTableInfo().qualifierColumnInfo().name(), key, Object.class) // + : Identifier.of(rootPath.append(property).getTableInfo().reverseColumnInfo().name(), 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..47f7898279 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.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.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,15 @@ 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); + AggregatePath.ColumnInfo qualifierColumnInfo = path.getTableInfo().qualifierColumnInfo(); + Assert.notNull(qualifierColumnInfo, () -> "Qualifier column must not be null for " + path); + SqlIdentifier keyColumn = qualifierColumnInfo.name(); 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..0087993cdb 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,8 @@ 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.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 +30,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 +45,10 @@ class JdbcBackReferencePropertyValueProvider implements PropertyValueProvider T getPropertyValue(RelationalPersistentProperty property) { - return (T) resultSet.getObject(basePath.extendBy(property).getReverseColumnNameAlias().getReference()); + return (T) resultSet.getObject(basePath.append(property).getTableInfo().reverseColumnInfo().alias().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..1a424aad8c 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,7 @@ */ package org.springframework.data.jdbc.core.convert; +import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -39,12 +40,23 @@ 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(), // + path.getTableInfo().reverseColumnInfo().name(), // value, // converter.getColumnType(path.getIdDefiningParentPath().getRequiredIdProperty()) // ); @@ -59,12 +71,26 @@ 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"); - identifier = identifier.withPart(path.getQualifierColumn(), value, path.getQualifierColumnType()); + identifier = identifier.withPart(path.getTableInfo().qualifierColumnInfo().name(), value, path.getTableInfo().qualifierColumnType()); 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..e0dcc76547 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,8 @@ 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.RelationalPersistentProperty; -import org.springframework.data.relational.core.sql.IdentifierProcessing; /** * {@link PropertyValueProvider} obtaining values from a {@link ResultSetAccessor}. @@ -29,15 +28,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 +57,11 @@ public boolean hasProperty(RelationalPersistentProperty property) { } private String getColumnName(RelationalPersistentProperty property) { - return basePath.extendBy(property).getColumnAlias().getReference(); + AggregatePath.ColumnInfo columnInfo = basePath.append(property).getColumnInfo(); + return columnInfo.alias().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..dbfa934973 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,7 +15,7 @@ */ 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.RelationalPersistentEntity; import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -52,18 +52,20 @@ Table getTable() { return table; } - Table getTable(PersistentPropertyPathExtension path) { + Table getTable(AggregatePath path) { - SqlIdentifier tableAlias = path.getTableAlias(); - Table table = Table.create(path.getQualifiedTableName()); + SqlIdentifier tableAlias = path.getTableInfo().tableAlias(); + Table table = Table.create(path.getTableInfo().qualifiedTableName()); return tableAlias == null ? table : table.as(tableAlias); } - Column getColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getColumnName()).as(path.getColumnAlias()); + Column getColumn(AggregatePath path) { + AggregatePath.ColumnInfo columnInfo = path.getColumnInfo(); + AggregatePath.ColumnInfo columnInfo1 = path.getColumnInfo(); + return getTable(path).column(columnInfo1.name()).as(columnInfo.alias()); } - Column getReverseColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getReverseColumnName()).as(path.getReverseColumnNameAlias()); + Column getReverseColumn(AggregatePath path) { + return getTable(path).column(path.getTableInfo().reverseColumnInfo().name()).as(path.getTableInfo().reverseColumnInfo().alias()); } } 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..60e69edec7 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,7 @@ 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.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -66,8 +66,13 @@ class SqlGenerator { static final SqlIdentifier IDS_SQL_PARAMETER = SqlIdentifier.unquoted("ids"); static final SqlIdentifier ROOT_ID_PARAMETER = SqlIdentifier.unquoted("rootId"); + /** + * Length of an aggregate path that is one longer then the root path. + */ + private static final int FIRST_NON_ROOT_LENTH = 2; + private final RelationalPersistentEntity entity; - private final MappingContext, RelationalPersistentProperty> mappingContext; + private final RelationalMappingContext mappingContext; private final RenderContext renderContext; private final SqlContext sqlContext; @@ -112,6 +117,40 @@ class SqlGenerator { this.dialect = dialect; } + /** + * When deleting entities there is a fundamental difference between deleting + *
    + *
  1. the aggregate root.
  2. + *
  3. a first level entity which still references the root id directly
  4. + *
  5. and all other entities which have to use a subselect to navigate from the id of the aggregate root to something + * referenced by the table in question.
  6. + *
+ * For paths of the second kind this method returns {@literal true}. + * + * @param path the path to analyze. + * @return If the given path is considered deeply nested. + */ + private static boolean isFirstNonRoot(AggregatePath path) { + return path.getLength() == FIRST_NON_ROOT_LENTH; + } + + /** + * When deleting entities there is a fundamental difference between deleting + *
    + *
  1. the aggregate root.
  2. + *
  3. a first level entity which still references the root id directly
  4. + *
  5. and all other entities which have to use a subselect to navigate from the id of the aggregate root to something + * referenced by the table in question.
  6. + *
+ * For paths of the third kind this method returns {@literal true}. + * + * @param path the path to analyze. + * @return If the given path is considered deeply nested. + */ + private static boolean isDeeplyNested(AggregatePath path) { + return path.getLength() > FIRST_NON_ROOT_LENTH; + } + /** * Construct an IN-condition based on a {@link Select Sub-Select} which selects the ids (or stand-ins for ids) of the * given {@literal path} to those that reference the root entities specified by the {@literal rootCondition}. @@ -121,25 +160,25 @@ 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 (parentPath.getLength() > 1) { + if (isDeeplyNested(parentPath)) { 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()); + Table subSelectTable = Table.create(parentPath.getTableInfo().qualifiedTableName()); + Column idColumn = subSelectTable.column(parentPath.getTableInfo().idColumnName()); + Column selectFilterColumn = subSelectTable.column(parentPath.getTableInfo().effectiveIdColumnName()); Condition innerCondition; - if (parentPath.getLength() == 1) { // if the parent is the root of the path + if (isFirstNonRoot(parentPath)) { // if the parent is the root of the path // apply the rootCondition innerCondition = rootCondition.apply(selectFilterColumn); @@ -216,9 +255,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, path.getTableInfo().qualifierColumnInfo(), path.isOrdered()); } /** @@ -233,7 +272,8 @@ String getFindAllByProperty(Identifier parentIdentifier, * keyColumn must not be {@code null}. * @return a SQL String. */ - String getFindAllByProperty(Identifier parentIdentifier, @Nullable SqlIdentifier keyColumn, boolean ordered) { + String getFindAllByProperty(Identifier parentIdentifier, @Nullable AggregatePath.ColumnInfo keyColumn, + boolean ordered) { Assert.isTrue(keyColumn != null || !ordered, "If the SQL statement should be ordered a keyColumn to order by must be provided"); @@ -243,14 +283,14 @@ String getFindAllByProperty(Identifier parentIdentifier, @Nullable SqlIdentifier SelectBuilder.SelectWhere builder = selectBuilder( // keyColumn == null // ? Collections.emptyList() // - : Collections.singleton(keyColumn) // + : Collections.singleton(keyColumn.name()) // ); Condition condition = buildConditionForBackReference(parentIdentifier, table); SelectBuilder.SelectWhereAndOr withWhereClause = builder.where(condition); Select select = ordered // - ? withWhereClause.orderBy(table.column(keyColumn).as(keyColumn)).build() // + ? withWhereClause.orderBy(table.column(keyColumn.name()).as(keyColumn.alias())).build() // : withWhereClause.build(); return render(select); @@ -399,7 +439,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 +463,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 +520,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 +577,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 @@ -572,7 +612,7 @@ Column getColumn(PersistentPropertyPathExtension path) { } @Nullable - Join getJoin(PersistentPropertyPathExtension path) { + Join getJoin(AggregatePath path) { if (!path.isEntity() || path.isEmbedded() || path.isMultiValued()) { return null; @@ -580,13 +620,13 @@ Join getJoin(PersistentPropertyPathExtension path) { Table currentTable = sqlContext.getTable(path); - PersistentPropertyPathExtension idDefiningParentPath = path.getIdDefiningParentPath(); + AggregatePath idDefiningParentPath = path.getIdDefiningParentPath(); Table parentTable = sqlContext.getTable(idDefiningParentPath); return new Join( // currentTable, // - currentTable.column(path.getReverseColumnName()), // - parentTable.column(idDefiningParentPath.getIdColumnName()) // + currentTable.column(path.getTableInfo().reverseColumnInfo().name()), // + parentTable.column(idDefiningParentPath.getTableInfo().idColumnName()) // ); } @@ -707,18 +747,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) { - Table table = Table.create(path.getQualifiedTableName()); + Table table = Table.create(path.getTableInfo().qualifiedTableName()); DeleteBuilder.DeleteWhere builder = Delete.builder() // .from(table); Delete delete; - Column filterColumn = table.column(path.getReverseColumnName()); + Column filterColumn = table.column(path.getTableInfo().reverseColumnInfo().name()); - if (path.getLength() == 1) { + if (isFirstNonRoot(path)) { delete = builder // .where(rootCondition.apply(filterColumn)) // @@ -926,10 +965,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 +999,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..b1cc21571b 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,9 @@ 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.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -117,8 +116,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 +125,30 @@ 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()) { - validateProperty(pathToValidate); - } + path.forEach(JdbcQueryCreator::validateProperty); } } } - 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())); + if (path.isRoot()) { + return; + } + + if (!path.getParentPath().isEmbedded() && path.getLength() > 2) { + 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 +243,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.getRequiredBaseProperty().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 +274,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 @@ -313,7 +310,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 +318,13 @@ Join getJoin(SqlContext sqlContext, PersistentPropertyPathExtension path) { Table currentTable = sqlContext.getTable(path); - PersistentPropertyPathExtension idDefiningParentPath = path.getIdDefiningParentPath(); + AggregatePath idDefiningParentPath = path.getIdDefiningParentPath(); Table parentTable = sqlContext.getTable(idDefiningParentPath); return new Join( // currentTable, // - currentTable.column(path.getReverseColumnName()), // - parentTable.column(idDefiningParentPath.getIdColumnName()) // + currentTable.column(path.getTableInfo().reverseColumnInfo().name()), // + parentTable.column(idDefiningParentPath.getTableInfo().idColumnName()) // ); } 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..4ddf9589a1 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,7 +15,7 @@ */ 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.RelationalPersistentEntity; import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -53,18 +53,21 @@ Table getTable() { return table; } - Table getTable(PersistentPropertyPathExtension path) { + Table getTable(AggregatePath path) { - SqlIdentifier tableAlias = path.getTableAlias(); - Table table = Table.create(path.getQualifiedTableName()); + SqlIdentifier tableAlias = path.getTableInfo().tableAlias(); + Table table = Table.create(path.getTableInfo().qualifiedTableName()); return tableAlias == null ? table : table.as(tableAlias); } - Column getColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getColumnName()).as(path.getColumnAlias()); + Column getColumn(AggregatePath path) { + AggregatePath.ColumnInfo columnInfo = path.getColumnInfo(); + AggregatePath.ColumnInfo columnInfo1 = path.getColumnInfo(); + return getTable(path).column(columnInfo1.name()).as(columnInfo.alias()); } - Column getReverseColumn(PersistentPropertyPathExtension path) { - return getTable(path).column(path.getReverseColumnName()).as(path.getReverseColumnNameAlias()); + Column getReverseColumn(AggregatePath path) { + return getTable(path).column(path.getTableInfo().reverseColumnInfo().name()) + .as(path.getTableInfo().reverseColumnInfo().alias()); } } 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 56decf3532..88f644852f 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 @@ -27,7 +27,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; @@ -156,8 +156,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) { @@ -165,7 +165,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 1030e45204..1a8df7c788 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 @@ -34,7 +34,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; @@ -248,8 +248,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) { @@ -257,7 +257,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 81% 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 5d0f7d7fcc..2dda418fc8 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 @@ -26,18 +26,22 @@ * * @author Jens Schauder */ -public final class PropertyPathTestingUtils { +public final class PersistentPropertyPathTestUtils { - private PropertyPathTestingUtils() { + private PersistentPropertyPathTestUtils() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } - 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..61a3856ab6 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,18 +32,17 @@ 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; import org.springframework.data.relational.core.dialect.PostgresDialect; import org.springframework.data.relational.core.dialect.SqlServerDialect; +import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.DefaultNamingStrategy; -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; @@ -389,7 +388,7 @@ void findAllByPropertyWithMultipartIdentifier() { void findAllByPropertyWithKey() { // this would get called when ListParent is th element type of a Map - String sql = sqlGenerator.getFindAllByProperty(BACKREF, unquoted("key-column"), false); + String sql = sqlGenerator.getFindAllByProperty(BACKREF, new AggregatePath.ColumnInfo(unquoted("key-column"),unquoted("key-column")), false); assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // @@ -412,7 +411,8 @@ void findAllByPropertyOrderedWithoutKey() { void findAllByPropertyWithKeyOrdered() { // this would get called when ListParent is th element type of a Map - String sql = sqlGenerator.getFindAllByProperty(BACKREF, unquoted("key-column"), true); + String sql = sqlGenerator.getFindAllByProperty(BACKREF, + new AggregatePath.ColumnInfo(unquoted("key-column"), unquoted("key-column")), true); assertThat(sql).isEqualTo("SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name, " // + "dummy_entity.x_other AS x_other, " // @@ -432,8 +432,10 @@ public void findAllByPropertyAvoidsDuplicateColumns() { final SqlGenerator sqlGenerator = createSqlGenerator(ReferencedEntity.class); final String sql = sqlGenerator.getFindAllByProperty( Identifier.of(quoted("id"), "parent-id-value", DummyEntity.class), // - quoted("X_L1ID"), // this key column collides with the name derived by the naming strategy for the id of - // ReferencedEntity. + new AggregatePath.ColumnInfo(quoted("X_L1ID"), quoted("X_L1ID")), // this key column collides with the name + // derived by the naming strategy for the id + // of + // ReferencedEntity. false); final String id = "referenced_entity.x_l1id AS x_l1id"; @@ -447,10 +449,11 @@ public void findAllByPropertyAvoidsDuplicateColumns() { void findAllByPropertyWithEmptyBackrefColumn() { Identifier emptyIdentifier = Identifier.of(EMPTY, 0, Object.class); - assertThatThrownBy(() -> sqlGenerator.getFindAllByProperty(emptyIdentifier, unquoted("key-column"), false)) // - .isInstanceOf(IllegalArgumentException.class) // - .hasMessageContaining( - "An empty SqlIdentifier can't be used in condition. Make sure that all composite primary keys are defined in the query"); + assertThatThrownBy(() -> sqlGenerator.getFindAllByProperty(emptyIdentifier, + new AggregatePath.ColumnInfo(unquoted("key-column"), unquoted("key-column")), false)) // + .isInstanceOf(IllegalArgumentException.class) // + .hasMessageContaining( + "An empty SqlIdentifier can't be used in condition. Make sure that all composite primary keys are defined in the query"); } @Test // DATAJDBC-219 @@ -574,15 +577,16 @@ void readOnlyPropertyIncludedIntoQuery_when_generateFindAllByPropertySql() { final SqlGenerator sqlGenerator = createSqlGenerator(EntityWithReadOnlyProperty.class); - assertThat(sqlGenerator.getFindAllByProperty(BACKREF, unquoted("key-column"), true)).isEqualToIgnoringCase( // - "SELECT " // - + "entity_with_read_only_property.x_id AS x_id, " // - + "entity_with_read_only_property.x_name AS x_name, " // - + "entity_with_read_only_property.x_read_only_value AS x_read_only_value, " // - + "entity_with_read_only_property.key-column AS key-column " // - + "FROM entity_with_read_only_property " // - + "WHERE entity_with_read_only_property.backref = :backref " // - + "ORDER BY key-column" // + assertThat(sqlGenerator.getFindAllByProperty(BACKREF, + new AggregatePath.ColumnInfo(unquoted("key-column"), unquoted("key-column")), true)).isEqualToIgnoringCase( // + "SELECT " // + + "entity_with_read_only_property.x_id AS x_id, " // + + "entity_with_read_only_property.x_name AS x_name, " // + + "entity_with_read_only_property.x_read_only_value AS x_read_only_value, " // + + "entity_with_read_only_property.key-column AS key-column " // + + "FROM entity_with_read_only_property " // + + "WHERE entity_with_read_only_property.backref = :backref " // + + "ORDER BY key-column" // ); } @@ -736,7 +740,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 @@ -935,11 +939,11 @@ private SqlIdentifier getAlias(Object maybeAliased) { 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))); + .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 d2afb16523..ee8d9dbabc 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 @@ -15,28 +15,29 @@ */ package org.springframework.data.jdbc.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 junit.framework.AssertionFailedError; + +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.List; +import java.util.UUID; + 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; -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 java.time.LocalDateTime; -import java.time.ZonedDateTime; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.SoftAssertions.*; -import static org.springframework.data.relational.core.sql.SqlIdentifier.*; - /** * Unit tests for the {@link BasicRelationalPersistentProperty}. * @@ -63,9 +64,10 @@ 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.getReverseColumnName(path.getRequiredBaseProperty().getOwner())) + .isEqualTo(quoted("dummy_column_name")); assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("dummy_key_column_name")); } @@ -76,10 +78,11 @@ 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")); + assertThat(listProperty.getReverseColumnName(path.getRequiredBaseProperty().getOwner())) + .isEqualTo(quoted("some_value")); } @Test // DATAJDBC-331 @@ -88,10 +91,11 @@ 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")); + assertThat(listProperty.getReverseColumnName(path.getRequiredBaseProperty().getOwner())) + .isEqualTo(quoted("override_id")); } @Test // GH-938 @@ -126,12 +130,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") @@ -142,8 +147,7 @@ private enum SomeEnum { @SuppressWarnings("unused") private static class DummyEntity { - @Id - private final Long id; + @Id private final Long id; private final SomeEnum someEnum; private final LocalDateTime localDateTime; private final ZonedDateTime zonedDateTime; @@ -152,13 +156,13 @@ private static class DummyEntity { private final UUID uuid; @MappedCollection(idColumn = "dummy_column_name", - keyColumn = "dummy_key_column_name") - private List someList; + keyColumn = "dummy_key_column_name") private List someList; // DATACMNS-106 private @Column("dummy_name") String name; - private DummyEntity(Long id, SomeEnum someEnum, LocalDateTime localDateTime, ZonedDateTime zonedDateTime, AggregateReference reference, List listField, UUID uuid) { + private DummyEntity(Long id, SomeEnum someEnum, LocalDateTime localDateTime, ZonedDateTime zonedDateTime, + AggregateReference reference, List listField, UUID uuid) { this.id = id; this.someEnum = someEnum; this.localDateTime = localDateTime; @@ -224,8 +228,7 @@ public void setName(String name) { private static class WithCollections { - @Column(value = "some_value") - List someList; + @Column(value = "some_value") List someList; @Column(value = "some_value") // @MappedCollection(idColumn = "override_id", keyColumn = "override_key") // diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mapping/model/DefaultNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mapping/model/DefaultNamingStrategyUnitTests.java index f051691598..b724717539 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mapping/model/DefaultNamingStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mapping/model/DefaultNamingStrategyUnitTests.java @@ -58,7 +58,7 @@ public void getColumnName() { } @Test // DATAJDBC-184 - public void getReverseColumnName() { + public void getReverseColumnInfoName() { assertThat(target.getReverseColumnName(persistentEntity.getPersistentProperty("dummySubEntities"))) .isEqualTo("dummy_entity"); } 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/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/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 0f3208760c..2a355c84ba 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; @@ -61,7 +62,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; @@ -71,8 +72,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()); } @@ -83,22 +83,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; @@ -116,7 +112,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..b1a546a689 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,6 +22,7 @@ import org.springframework.data.convert.EntityWriter; import org.springframework.data.mapping.PersistentPropertyPath; +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.RelationalPersistentProperty; @@ -126,7 +127,7 @@ private void forAllTableRepresentingPaths(Class entityType, Consumer> pathConsumer) { context.findPersistentPropertyPaths(entityType, property -> property.isEntity() && !property.isEmbedded()) // - .filter(PersistentPropertyPathExtension::isWritable) // + .filter(path -> context.getAggregatePath(path).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..5550599243 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,6 +24,7 @@ import java.util.Map; import org.springframework.data.mapping.PersistentPropertyPath; +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; @@ -63,7 +64,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(ppp -> context.getAggregatePath(ppp).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..0ad660f907 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java @@ -0,0 +1,342 @@ +/* + * 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 java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Represents a path within an aggregate starting from the aggregate root. The path can be iterated from the leaf to its + * root. + * + * @since 3.2 + * @author Jens Schauder + * @author Mark Paluch + */ +public interface AggregatePath extends Iterable { + + /** + * 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(); + + /** + * 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); + + /** + * @return {@literal true} if this is a root path for the underlying type. + */ + boolean isRoot(); + + /** + * Returns the path length for the aggregate path. + * + * @return the path length for the aggregate path + */ + default int getLength() { + return 1 + (isRoot() ? 0 : getRequiredPersistentPropertyPath().getLength()); + } + + boolean isWritable(); + + /** + * @return {@literal true} when this is an empty path or the path references an entity. + */ + boolean isEntity(); + + /** + * 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(); + + /** + * 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. // TODO: why does this return true if the parent entity is a collection? + * This seems to mix some concepts that belong to somewhere else. // TODO: Multi-valued could be understood for + * embeddables with more than one column (i.e. composite primary keys) + * + * @return {@literal true} if the path contains a multivalued element. + */ + boolean isMultiValued(); + + /** + * @return {@literal true} when this is references a {@link java.util.List} or {@link java.util.Map}. + */ + boolean isQualified(); + + /** + * @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.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(); + + /** + * @return {@literal true} if this path represents an entity which has an identifier attribute. + */ + boolean hasIdProperty(); + + RelationalPersistentProperty getRequiredIdProperty(); + + /** + * @return the persistent property path if the path is not a {@link #isRoot() root} path. + * @throws IllegalStateException if the current path is a {@link #isRoot() root} path. + * @see PersistentPropertyPath#getBaseProperty() + */ + PersistentPropertyPath getRequiredPersistentPropertyPath(); + + /** + * @return the base property. + * @throws IllegalStateException if the current path is a {@link #isRoot() root} path. + * @see PersistentPropertyPath#getBaseProperty() + */ + default RelationalPersistentProperty getRequiredBaseProperty() { + return getRequiredPersistentPropertyPath().getBaseProperty(); + } + + /** + * @return the leaf property. + * @throws IllegalStateException if the current path is a {@link #isRoot() root} path. + * @see PersistentPropertyPath#getLeafProperty() + */ + default RelationalPersistentProperty getRequiredLeafProperty() { + return getRequiredPersistentPropertyPath().getLeafProperty(); + } + + /** + * 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. + */ + default RelationalPersistentEntity getRequiredLeafEntity() { + + RelationalPersistentEntity entity = getLeafEntity(); + + if (entity == null) { + + throw new IllegalStateException(String.format("Couldn't resolve leaf PersistentEntity for type %s", + getRequiredLeafProperty().getActualType())); + } + + return entity; + } + + /** + * Returns the dot based path notation using {@link PersistentProperty#getName()}. + * + * @return will never be {@literal null}. + */ + String toDotPath(); + + // TODO: Conceptually, AggregatePath works with properties. The mapping into columns and tables should reside in a + // utility that can distinguish whether a property maps to one or many columns (e.g. embedded) and the same for + // identifier columns. + default TableInfo getTableInfo() { + return TableInfo.of(this); + } + + default ColumnInfo getColumnInfo() { + return ColumnInfo.of(this); + } + + /** + * Filter the AggregatePath returning the first item matching the given {@link Predicate}. + * + * @param predicate must not be {@literal null}. + * @return the first matching element or {@literal null}. + */ + @Nullable + default AggregatePath filter(Predicate predicate) { + + for (AggregatePath item : this) { + if (predicate.test(item)) { + return item; + } + } + + return null; + } + + /** + * Creates a non-parallel {@link Stream} of the underlying {@link Iterable}. + * + * @return will never be {@literal null}. + */ + default Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + // path navigation + + /** + * 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}. TODO: throws + * NoSuchElementException: No value present for empty paths + */ + AggregatePath getIdDefiningParentPath(); + + record TableInfo( + + /* + * 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. + */ + SqlIdentifier qualifiedTableName, + + /* + * The alias used for the table on which this path is based. + */ + @Nullable SqlIdentifier tableAlias, + + ColumnInfo reverseColumnInfo, + + /* + * The column used for the list index or map key of the leaf property of this path. + */ + @Nullable ColumnInfo qualifierColumnInfo, + + /* + * The type of the qualifier column of the leaf property of this path or {@literal null} if this is not + * applicable. + */ + @Nullable Class qualifierColumnType, + + /* + * The column name of the id column of the ancestor path that represents an actual table. + */ + SqlIdentifier idColumnName, + + /* + * If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse + * column is returned. + */ + SqlIdentifier effectiveIdColumnName) { + + static TableInfo of(AggregatePath path) { + + AggregatePath tableOwner = AggregatePathTraversal.getTableOwningPath(path); + + RelationalPersistentEntity leafEntity = tableOwner.getRequiredLeafEntity(); + SqlIdentifier qualifiedTableName = leafEntity.getQualifiedTableName(); + + SqlIdentifier tableAlias = tableOwner.isRoot() ? null : AggregatePathTableUtils.constructTableAlias(tableOwner); + + ColumnInfo reverseColumnInfo = null; + if (!tableOwner.isRoot()) { + + AggregatePath idDefiningParentPath = tableOwner.getIdDefiningParentPath(); + RelationalPersistentProperty leafProperty = tableOwner.getRequiredLeafProperty(); + + SqlIdentifier reverseColumnName = leafProperty + .getReverseColumnName(idDefiningParentPath.getRequiredLeafEntity()); + + reverseColumnInfo = new ColumnInfo(reverseColumnName, + AggregatePathTableUtils.prefixWithTableAlias(path, reverseColumnName)); + } + + ColumnInfo qualifierColumnInfo = null; + if (!path.isRoot()) { + + SqlIdentifier keyColumn = path.getRequiredLeafProperty().getKeyColumn(); + if (keyColumn != null) { + qualifierColumnInfo = new ColumnInfo(keyColumn, keyColumn); + } + } + + Class qualifierColumnType = null; + if (!path.isRoot() && path.getRequiredLeafProperty().isQualified()) { + qualifierColumnType = path.getRequiredLeafProperty().getQualifierColumnType(); + } + + SqlIdentifier idColumnName = leafEntity.hasIdProperty() ? leafEntity.getIdColumn() : null; + + SqlIdentifier effectiveIdColumnName = tableOwner.isRoot() ? idColumnName : reverseColumnInfo.name(); + + return new TableInfo(qualifiedTableName, tableAlias, reverseColumnInfo, qualifierColumnInfo, qualifierColumnType, + idColumnName, effectiveIdColumnName); + + } + } + + record ColumnInfo( + + /* The name of the column used to represent this property in the database. */ + SqlIdentifier name, /* The alias for the column used to represent this property in the database. */ + SqlIdentifier alias) { + + /** + * Create a {@link ColumnInfo} from an aggregate path. ColumnInfo can be created for simple type single-value + * properties only. + * + * @param path + * @return the {@link ColumnInfo}. + * @throws IllegalArgumentException if the path is {@link #isRoot()}, {@link #isEmbedded()} or + * {@link #isMultiValued()}. + */ + static ColumnInfo of(AggregatePath path) { + + Assert.notNull(path, "AggregatePath must not be null"); + Assert.isTrue(!path.isRoot(), () -> "Cannot obtain ColumnInfo for root path"); + Assert.isTrue(!path.isEmbedded(), () -> "Cannot obtain ColumnInfo for embedded path"); + + // TODO: Multi-valued paths cannot be represented with a single column + // Assert.isTrue(!path.isMultiValued(), () -> "Cannot obtain ColumnInfo for multi-valued path"); + + SqlIdentifier name = AggregatePathTableUtils.assembleColumnName(path, + path.getRequiredLeafProperty().getColumnName()); + return new ColumnInfo(name, AggregatePathTableUtils.prefixWithTableAlias(path, name)); + } + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathTableUtils.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathTableUtils.java new file mode 100644 index 0000000000..fa02220f8e --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathTableUtils.java @@ -0,0 +1,94 @@ +/* + * 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.Collections; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +import org.springframework.data.relational.core.sql.SqlIdentifier; + +/** + * Utility methods to derive table and column information from {@link AggregatePath}. + * + * @author Mark Paluch + * @since 3.2 + */ +class AggregatePathTableUtils { + + public static SqlIdentifier assembleColumnName(AggregatePath path, SqlIdentifier suffix) { + return suffix.transform(constructEmbeddedPrefix(path)::concat); + } + + private static String constructEmbeddedPrefix(AggregatePath path) { + + return path.stream() // + .filter(p -> p != path) // + .takeWhile(AggregatePath::isEmbedded).map(p -> p.getRequiredLeafProperty().getEmbeddedPrefix()) // + .collect(new ReverseJoinCollector()); + } + + public static SqlIdentifier prefixWithTableAlias(AggregatePath path, SqlIdentifier columnName) { + + AggregatePath tableOwner = AggregatePathTraversal.getTableOwningPath(path); + SqlIdentifier tableAlias = tableOwner.isRoot() ? null : constructTableAlias(tableOwner); + + return tableAlias == null ? columnName : columnName.transform(name -> tableAlias.getReference() + "_" + name); + } + + public static SqlIdentifier constructTableAlias(AggregatePath path) { + + String alias = path.stream() // + .filter(p -> !p.isRoot()) // + .map(p -> p.isEmbedded() // + ? p.getRequiredLeafProperty().getEmbeddedPrefix()// + : p.getRequiredLeafProperty().getName() + (p == path ? "" : "_") // + ) // + .collect(new ReverseJoinCollector()); + return SqlIdentifier.quoted(alias); + } + + private static class ReverseJoinCollector implements Collector { + @Override + public Supplier supplier() { + return StringBuilder::new; + } + + @Override + public BiConsumer accumulator() { + return ((stringBuilder, s) -> stringBuilder.insert(0, s)); + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> b.append(a); + } + + @Override + public Function finisher() { + return StringBuilder::toString; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathTraversal.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathTraversal.java new file mode 100644 index 0000000000..450761d73f --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePathTraversal.java @@ -0,0 +1,49 @@ +/* + * 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.NoSuchElementException; +import java.util.function.Predicate; + +/** + * @author Mark Paluch + */ +class AggregatePathTraversal { + + public static AggregatePath getIdDefiningPath(AggregatePath aggregatePath) { + + Predicate idDefiningPathFilter = ap -> !ap.equals(aggregatePath) + && (ap.isRoot() || ap.hasIdProperty()); + + AggregatePath result = aggregatePath.filter(idDefiningPathFilter); + if (result == null) { + throw new NoSuchElementException(); + } + return result; + } + + public static AggregatePath getTableOwningPath(AggregatePath aggregatePath) { + + Predicate idDefiningPathFilter = ap -> ap.isEntity() && !ap.isEmbedded(); + + AggregatePath result = aggregatePath.filter(idDefiningPathFilter); + if (result == null) { + throw new NoSuchElementException(); + } + return result; + } + +} 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..b5f335c8c8 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,13 @@ public SqlIdentifier getReverseColumnName(PersistentPropertyPathExtension path) return createSqlIdentifier(expressionEvaluator.evaluate(collectionIdColumnNameExpression)); } + @Override + public SqlIdentifier getReverseColumnName(RelationalPersistentEntity owner) { + + return collectionIdColumnName.get() + .orElseGet(() -> createDerivedSqlIdentifier(this.namingStrategy.getReverseColumnName(owner))); + } + @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..71632942cf 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 @@ -72,6 +72,11 @@ public String getReverseColumnName(RelationalPersistentProperty property) { return delegate.getReverseColumnName(property); } + @Override + public String getReverseColumnName(RelationalPersistentEntity owner) { + return delegate.getReverseColumnName(owner); + } + @Override public String getSchema() { return schema.get(); 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..f924ba9d39 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java @@ -0,0 +1,255 @@ +/* + * 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.NoSuchElementException; +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 + * @author Mark Paluch + */ +class DefaultAggregatePath implements AggregatePath { + + private final RelationalMappingContext context; + + private final @Nullable RelationalPersistentEntity rootType; + + private final @Nullable PersistentPropertyPath path; + + @SuppressWarnings("unchecked") + 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 = (PersistentPropertyPath) 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; + } + + /** + * 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."); + } + + PersistentPropertyPath path = getRequiredPersistentPropertyPath(); + + if (path.getLength() == 1) { + return context.getAggregatePath(path.getLeafProperty().getOwner()); + } + + return context.getAggregatePath(path.getParentPath()); + } + + @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 boolean isRoot() { + return path == null; + } + + @Override + public boolean isWritable() { + return stream().allMatch(path -> path.isRoot() || path.getRequiredLeafProperty().isWritable()); + } + + @Override + public boolean isEntity() { + return isRoot() || getRequiredLeafProperty().isEntity(); + } + + @Override + public boolean isEmbedded() { + return !isRoot() && getRequiredLeafProperty().isEmbedded(); + } + + @Override + public boolean isMultiValued() { + + return !isRoot() && // + (getRequiredLeafProperty().isCollectionLike() // + || getRequiredLeafProperty().isQualified() // + // TODO: Considering the parent as multi-valued burries the scope of this method. + // this needs to be resolved + || getParentPath().isMultiValued() // + ); + } + + @Override + public boolean isQualified() { + return !isRoot() && getRequiredLeafProperty().isQualified(); + } + + @Override + public boolean isMap() { + return !isRoot() && getRequiredLeafProperty().isMap(); + } + + @Override + public boolean isCollectionLike() { + return !isRoot() && getRequiredLeafProperty().isCollectionLike(); + } + + @Override + public boolean isOrdered() { + return !isRoot() && getRequiredLeafProperty().isOrdered(); + } + + @Override + public boolean hasIdProperty() { + + RelationalPersistentEntity leafEntity = getLeafEntity(); + return leafEntity != null && leafEntity.hasIdProperty(); + } + + @Override + public RelationalPersistentProperty getRequiredIdProperty() { + return isRoot() ? rootType.getRequiredIdProperty() : getRequiredLeafEntity().getRequiredIdProperty(); + } + + @Override + public PersistentPropertyPath getRequiredPersistentPropertyPath() { + + Assert.state(path != null, "Root Aggregate Paths are not associated with a PersistentPropertyPath"); + return path; + } + + @Override + public RelationalPersistentEntity getLeafEntity() { + return isRoot() ? rootType : context.getPersistentEntity(getRequiredLeafProperty().getActualType()); + } + + @Override + public String toDotPath() { + return isRoot() ? "" : getRequiredPersistentPropertyPath().toDotPath(); + } + + @Override + public AggregatePath getIdDefiningParentPath() { + return AggregatePathTraversal.getIdDefiningPath(this); + } + + /** + * 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 AggregatePath getTableOwningAncestor() { + return AggregatePathTraversal.getTableOwningPath(this); + } + + @Override + public String toString() { + return "AggregatePath[" + + (rootType == null ? path.getBaseProperty().getOwner().getType().getName() : rootType.getName()) + "]" + + ((isRoot()) ? "/" : path.toDotPath()); + } + + + + @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); + } + + /** + * Creates an {@link Iterator} that iterates over the current path and all ancestors. It will start with the current + * path, followed by its parent until ending with the root. + */ + @Override + public Iterator iterator() { + return new AggregatePathIterator(this); + } + + private static class AggregatePathIterator implements Iterator { + + private @Nullable AggregatePath current; + + public AggregatePathIterator(AggregatePath current) { + this.current = current; + } + + @Override + public boolean hasNext() { + return current != null; + } + + @Override + public AggregatePath next() { + + AggregatePath element = current; + + if (element == null) { + throw new NoSuchElementException(); + } + + current = element.isRoot() ? null : element.getParentPath(); + + return element; + } + } + +} 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..8485eeb813 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,12 +61,17 @@ public String getReverseColumnName(PersistentPropertyPathExtension path) { return getColumnNameReferencing(leafEntity); } - private String getColumnNameReferencing(RelationalPersistentEntity leafEntity) { + @Override + public String getReverseColumnName(RelationalPersistentEntity parent) { + return getColumnNameReferencing(parent); + } + + private String getColumnNameReferencing(RelationalPersistentEntity entity) { if (foreignKeyNaming == ForeignKeyNaming.IGNORE_RENAMING) { - return getTableName(leafEntity.getType()); + return getTableName(entity.getType()); } - return leafEntity.getTableName().getReference(); + return entity.getTableName().getReference(); } } 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..55a070791b 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,16 @@ 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.getRequiredLeafEntity()); + } + + default String getReverseColumnName(RelationalPersistentEntity owner) { + return getTableName(owner.getType()); } /** @@ -104,4 +111,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..eb7be56d0d 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; @@ -86,7 +87,7 @@ public static boolean isWritable(@Nullable PersistentPropertyPath 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..6d102c7acf 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); @@ -125,8 +131,39 @@ protected RelationalPersistentProperty createPersistentProperty(Property propert } protected void applyDefaults(BasicRelationalPersistentProperty persistentProperty) { + persistentProperty.setForceQuote(isForceQuote()); 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) { + + AggregatePath aggregatePath = aggregatePathCache.get(path); + if (aggregatePath == null) { + + aggregatePath = new DefaultAggregatePath(this, path); + aggregatePathCache.put(path, aggregatePath); + } + + return aggregatePath; + } + + public AggregatePath getAggregatePath(RelationalPersistentEntity type) { + + AggregatePath aggregatePath = aggregatePathCache.get(type); + if (aggregatePath == null) { + + aggregatePath = new DefaultAggregatePath(this, type); + aggregatePathCache.put(type, aggregatePath); + } + + return aggregatePath; + } } 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..1b736f299a 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 @@ -20,7 +20,7 @@ import org.springframework.lang.Nullable; /** - * A {@link PersistentProperty} with methods for additional JDBC/RDBMS related meta data. + * A {@link PersistentProperty} with methods for additional RDBMS related metadata based on columns. * * @author Jens Schauder * @author Oliver Gierke @@ -38,8 +38,18 @@ public interface RelationalPersistentProperty extends PersistentProperty getOwner(); + /** + * @deprecated use {@link #getReverseColumnName(RelationalPersistentEntity)} instead + */ + @Deprecated(since = "3.2", forRemoval = true) SqlIdentifier getReverseColumnName(PersistentPropertyPathExtension path); + /** + * @param owner the owning entity. + * @return the column name to represent the owning side. + */ + SqlIdentifier getReverseColumnName(RelationalPersistentEntity owner); + @Nullable SqlIdentifier getKeyColumn(); @@ -78,7 +88,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/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 68f92fc1ea..0fcda4b032 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 @@ -15,12 +15,10 @@ */ package org.springframework.data.relational.core.mapping; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.relational.core.sql.SqlIdentifier.*; + import junit.framework.AssertionFailedError; -import org.assertj.core.api.SoftAssertions; -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.Embedded.OnEmpty; import java.time.LocalDateTime; import java.time.ZonedDateTime; @@ -28,8 +26,11 @@ import java.util.List; import java.util.function.BiConsumer; -import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.relational.core.sql.SqlIdentifier.*; +import org.assertj.core.api.SoftAssertions; +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.Embedded.OnEmpty; /** * Unit tests for the {@link BasicRelationalPersistentProperty}. @@ -62,7 +63,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(path.getLeafProperty().getOwner())) .isEqualTo(quoted("dummy_column_name")); assertThat(listProperty.getKeyColumn()).isEqualTo(quoted("dummy_key_column_name")); } @@ -87,7 +88,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-relational/src/test/java/org/springframework/data/relational/core/mapping/DefaultAggregatePathUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/DefaultAggregatePathUnitTests.java new file mode 100644 index 0000000000..5c5b9df991 --- /dev/null +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/DefaultAggregatePathUnitTests.java @@ -0,0 +1,522 @@ +/* + * 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 java.util.stream.Collectors; + +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; + +/** + * Tests for {@link AggregatePath}. + * + * @author Jens Schauder + * @author Mark Paluch + */ +class DefaultAggregatePathUnitTests { + 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(path("second.third2.value").getIdDefiningParentPath()).isEqualTo(path()); + softly.assertThat(path("second.third.value").getIdDefiningParentPath()).isEqualTo(path()); + softly.assertThat(path("secondList.third2.value").getIdDefiningParentPath()).isEqualTo(path()); + softly.assertThat(path("secondList.third.value").getIdDefiningParentPath()).isEqualTo(path()); + softly.assertThat(path("second2.third2.value").getIdDefiningParentPath()).isEqualTo(path()); + softly.assertThat(path("second2.third.value").getIdDefiningParentPath()).isEqualTo(path()); + softly.assertThat(path("withId.second.third2.value").getIdDefiningParentPath()).isEqualTo(path("withId")); + softly.assertThat(path("withId.second.third.value").getIdDefiningParentPath()).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(path("second.third2").getTableInfo().reverseColumnInfo().name()) + .isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(path("second.third").getTableInfo().reverseColumnInfo().name()) + .isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(path("secondList.third2").getTableInfo().reverseColumnInfo().name()) + .isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(path("secondList.third").getTableInfo().reverseColumnInfo().name()) + .isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(path("second2.third").getTableInfo().reverseColumnInfo().name()) + .isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(path("withId.second.third2.value").getTableInfo().reverseColumnInfo().name()) + .isEqualTo(quoted("WITH_ID")); + softly.assertThat(path("withId.second.third").getTableInfo().reverseColumnInfo().name()) + .isEqualTo(quoted("WITH_ID")); + softly.assertThat(path("withId.second2.third").getTableInfo().reverseColumnInfo().name()) + .isEqualTo(quoted("WITH_ID")); + }); + } + + @Test // GH-1525 + void getQualifierColumn() { + + assertSoftly(softly -> { + + softly.assertThat(path().getTableInfo().qualifierColumnInfo()).isEqualTo(null); + softly.assertThat(path("second.third").getTableInfo().qualifierColumnInfo()).isEqualTo(null); + softly.assertThat(path("secondList.third2").getTableInfo().qualifierColumnInfo()).isEqualTo(null); + softly.assertThat(path("secondList").getTableInfo().qualifierColumnInfo().name()) + .isEqualTo(SqlIdentifier.quoted("DUMMY_ENTITY_KEY")); + + }); + } + + @Test // GH-1525 + void getQualifierColumnType() { + + assertSoftly(softly -> { + + softly.assertThat(path().getTableInfo().qualifierColumnType()).isEqualTo(null); + softly.assertThat(path("second.third").getTableInfo().qualifierColumnType()).isEqualTo(null); + softly.assertThat(path("secondList.third2").getTableInfo().qualifierColumnType()).isEqualTo(null); + softly.assertThat(path("secondList").getTableInfo().qualifierColumnType()).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(context.getAggregatePath(createSimplePath("withId")).isWritable()) + .describedAs("simple path is writable").isTrue(); + softly.assertThat(context.getAggregatePath(createSimplePath("secondList.third2")).isWritable()) + .describedAs("long path is writable").isTrue(); + softly.assertThat(context.getAggregatePath(createSimplePath("second")).isWritable()) + .describedAs("simple read only path is not writable").isFalse(); + softly.assertThat(context.getAggregatePath(createSimplePath("second.third")).isWritable()) + .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(); // this seems wrong as third2 is an + // embedded path into Second, held by + // List (so the parent is + // multi-valued but not third2). + // TODO: This test fails because MultiValued considers parents. + // softly.assertThat(path("secondList.third.value").isMultiValued()).isFalse(); + 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(path().getTableInfo().tableAlias()).isEqualTo(null); + softly.assertThat(path("second").getTableInfo().tableAlias()).isEqualTo(quoted("second")); + softly.assertThat(path("second.third2").getTableInfo().tableAlias()).isEqualTo(quoted("second")); + softly.assertThat(path("second.third2.value").getTableInfo().tableAlias()).isEqualTo(quoted("second")); + softly.assertThat(path("second.third").getTableInfo().tableAlias()).isEqualTo(quoted("second_third")); // missing + // _ + softly.assertThat(path("second.third.value").getTableInfo().tableAlias()).isEqualTo(quoted("second_third")); // missing + // _ + softly.assertThat(path("secondList.third2").getTableInfo().tableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(path("secondList.third2.value").getTableInfo().tableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(path("secondList.third").getTableInfo().tableAlias()).isEqualTo(quoted("secondList_third")); // missing + // _ + softly.assertThat(path("secondList.third.value").getTableInfo().tableAlias()) + .isEqualTo(quoted("secondList_third")); // missing _ + softly.assertThat(path("secondList").getTableInfo().tableAlias()).isEqualTo(quoted("secondList")); + softly.assertThat(path("second2.third").getTableInfo().tableAlias()).isEqualTo(quoted("secthird")); + softly.assertThat(path("second3.third").getTableInfo().tableAlias()).isEqualTo(quoted("third")); + }); + } + + @Test // GH-1525 + void getTableName() { + + assertSoftly(softly -> { + + softly.assertThat(path().getTableInfo().qualifiedTableName()).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(path("second").getTableInfo().qualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(path("second.third2").getTableInfo().qualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(path("second.third2.value").getTableInfo().qualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(path("secondList.third2").getTableInfo().qualifiedTableName()).isEqualTo(quoted("SECOND")); + softly.assertThat(path("secondList.third2.value").getTableInfo().qualifiedTableName()) + .isEqualTo(quoted("SECOND")); + softly.assertThat(path("secondList").getTableInfo().qualifiedTableName()).isEqualTo(quoted("SECOND")); + }); + } + + @Test // GH-1525 + void getColumnName() { + + assertSoftly(softly -> { + + softly.assertThat(path("second.third2.value").getColumnInfo().name()).isEqualTo(quoted("THRDVALUE")); + softly.assertThat(path("second.third.value").getColumnInfo().name()).isEqualTo(quoted("VALUE")); + softly.assertThat(path("secondList.third2.value").getColumnInfo().name()).isEqualTo(quoted("THRDVALUE")); + softly.assertThat(path("secondList.third.value").getColumnInfo().name()).isEqualTo(quoted("VALUE")); + softly.assertThat(path("second2.third2.value").getColumnInfo().name()).isEqualTo(quoted("SECTHRDVALUE")); + softly.assertThat(path("second2.third.value").getColumnInfo().name()).isEqualTo(quoted("VALUE")); + }); + } + + @Test // GH-1525 + void getColumnAlias() { + + assertSoftly(softly -> { + + softly.assertThat(path("second.third2.value").getColumnInfo().alias()).isEqualTo(quoted("SECOND_THRDVALUE")); + softly.assertThat(path("second.third.value").getColumnInfo().alias()).isEqualTo(quoted("SECOND_THIRD_VALUE")); + softly.assertThat(path("secondList.third2.value").getColumnInfo().alias()) + .isEqualTo(quoted("SECONDLIST_THRDVALUE")); + softly.assertThat(path("secondList.third.value").getColumnInfo().alias()) + .isEqualTo(quoted("SECONDLIST_THIRD_VALUE")); + softly.assertThat(path("second2.third2.value").getColumnInfo().alias()).isEqualTo(quoted("SECTHRDVALUE")); + softly.assertThat(path("second2.third.value").getColumnInfo().alias()).isEqualTo(quoted("SECTHIRD_VALUE")); + }); + } + + @Test // GH-1525 + void getReverseColumnAlias() { + + assertSoftly(softly -> { + + softly.assertThat(path("second.third2.value").getTableInfo().reverseColumnInfo().alias()) + .isEqualTo(quoted("SECOND_DUMMY_ENTITY")); + softly.assertThat(path("second.third.value").getTableInfo().reverseColumnInfo().alias()) + .isEqualTo(quoted("SECOND_THIRD_DUMMY_ENTITY")); + softly.assertThat(path("secondList.third2.value").getTableInfo().reverseColumnInfo().alias()) + .isEqualTo(quoted("SECONDLIST_DUMMY_ENTITY")); + softly.assertThat(path("secondList.third.value").getTableInfo().reverseColumnInfo().alias()) + .isEqualTo(quoted("SECONDLIST_THIRD_DUMMY_ENTITY")); + softly.assertThat(path("second2.third.value").getTableInfo().reverseColumnInfo().alias()) + .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").getRequiredBaseProperty()) + .isEqualTo(entity.getPersistentProperty("second")); + softly.assertThat(path("second.third.value").getRequiredBaseProperty()) + .isEqualTo(entity.getPersistentProperty("second")); + softly.assertThat(path("secondList.third2.value").getRequiredBaseProperty()) + .isEqualTo(entity.getPersistentProperty("secondList")); + softly.assertThatThrownBy(() -> path().getRequiredBaseProperty()).isInstanceOf(IllegalStateException.class); + }); + } + + @Test // GH-1525 + void getIdColumnName() { + + assertSoftly(softly -> { + + softly.assertThat(path().getTableInfo().idColumnName()).isEqualTo(quoted("ENTITY_ID")); + softly.assertThat(path("withId").getTableInfo().idColumnName()).isEqualTo(quoted("WITH_ID_ID")); + + softly.assertThat(path("second").getTableInfo().idColumnName()).isNull(); + softly.assertThat(path("second.third2").getTableInfo().idColumnName()).isNull(); + softly.assertThat(path("withId.second").getTableInfo().idColumnName()).isNull(); + }); + } + + @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(path().getTableInfo().effectiveIdColumnName()).isEqualTo(quoted("ENTITY_ID")); + softly.assertThat(path("second.third2").getTableInfo().effectiveIdColumnName()).isEqualTo(quoted("DUMMY_ENTITY")); + softly.assertThat(path("withId.second.third").getTableInfo().effectiveIdColumnName()) + .isEqualTo(quoted("WITH_ID")); + softly.assertThat(path("withId.second.third2.value").getTableInfo().effectiveIdColumnName()) + .isEqualTo(quoted("WITH_ID")); + }); + } + + @Test // GH-1525 + void getLength() { + + assertThat(path().getLength()).isEqualTo(1); + assertThat(path().stream().collect(Collectors.toList())).hasSize(1); + + assertThat(path("second.third2").getLength()).isEqualTo(3); + assertThat(path("second.third2").stream().collect(Collectors.toList())).hasSize(3); + + assertThat(path("withId.second.third").getLength()).isEqualTo(4); + assertThat(path("withId.second.third2.value").getLength()).isEqualTo(5); + } + + 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-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 75% 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 3ebad2fac6..f31dc58f8e 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,9 +13,10 @@ * 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 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; @@ -31,10 +32,14 @@ private 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; }