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 d74246986b..1711b388f7 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 @@ -61,6 +61,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent private final @Nullable ValueExpression collectionKeyColumnNameExpression; private final boolean isEmbedded; private final String embeddedPrefix; + private final String embeddedSuffix; private final NamingStrategy namingStrategy; private boolean forceQuote = true; @@ -89,6 +90,9 @@ public BasicRelationalPersistentProperty(Property property, PersistentEntity> collectionIdColumnName = null; Lazy collectionKeyColumnName = Lazy @@ -269,6 +273,11 @@ public String getEmbeddedPrefix() { return isEmbedded() ? embeddedPrefix : ""; } + @Override + public String getEmbeddedSuffix() { + return isEmbedded() ? embeddedSuffix : ""; + } + @Override public boolean shouldCreateEmptyEmbedded() { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java index 9ffbfa63be..8738a75b7d 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Embedded.java @@ -56,6 +56,11 @@ */ String prefix() default ""; + /** + * @return suffix for columns in the embedded value object. An empty {@link String} by default. + */ + String suffix() default ""; + /** * Load strategy to be used {@link Embedded#onEmpty()}. * @@ -104,6 +109,12 @@ enum OnEmpty { */ @AliasFor(annotation = Embedded.class, attribute = "prefix") String prefix() default ""; + + /** + * @return suffix for columns in the embedded value object. An empty {@link String} by default. + */ + @AliasFor(annotation = Embedded.class, attribute = "suffix") + String suffix() default ""; } /** @@ -144,5 +155,11 @@ enum OnEmpty { */ @AliasFor(annotation = Embedded.class, attribute = "prefix") String prefix() default ""; + + /** + * @return suffix for columns in the embedded value object. An empty {@link String} by default. + */ + @AliasFor(annotation = Embedded.class, attribute = "suffix") + String suffix() default ""; } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedContext.java index 589c403bca..b7b88a75bc 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedContext.java @@ -32,4 +32,14 @@ public String withEmbeddedPrefix(String name) { String embeddedPrefix = ownerProperty.getEmbeddedPrefix(); return embeddedPrefix + name; } + + public String withEmbeddedSuffix(String name) { + + if (!ownerProperty.isEmbedded()) { + return name; + } + + String embeddedSuffix = ownerProperty.getEmbeddedSuffix(); + return name + embeddedSuffix; + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java index 3006b0dd14..6a31ee97df 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java @@ -54,9 +54,16 @@ public String getEmbeddedPrefix() { return context.withEmbeddedPrefix(delegate.getEmbeddedPrefix()); } + @Override + public String getEmbeddedSuffix() { + return context.withEmbeddedSuffix(delegate.getEmbeddedSuffix()); + } + @Override public SqlIdentifier getColumnName() { - return delegate.getColumnName().transform(context::withEmbeddedPrefix); + return delegate.getColumnName() + .transform(context::withEmbeddedPrefix) + .transform(context::withEmbeddedSuffix); } @Override 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 ad61ba4a6a..8e4870dbe7 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 @@ -83,6 +83,13 @@ default String getEmbeddedPrefix() { return ""; } + /** + * @return Suffix for embedded columns. If the column is not embedded the return value is empty. + */ + default String getEmbeddedSuffix() { + return ""; + } + /** * Returns whether an empty embedded object is supposed to be created for this property. */ 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 c3349186dd..35506d4613 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 @@ -93,7 +93,7 @@ void shouldEvaluateMappedCollectionExpressions() { assertThat(property.getKeyColumn()).isEqualTo(quoted("key_col")); } - @Test // DATAJDBC-111 + @Test // DATAJDBC-111, GH-2303 void detectsEmbeddedEntity() { final RelationalPersistentEntity requiredPersistentEntity = context @@ -101,11 +101,12 @@ void detectsEmbeddedEntity() { SoftAssertions softly = new SoftAssertions(); - BiConsumer checkEmbedded = (name, prefix) -> { - + BiConsumer checkEmbedded = (name, args) -> { + String expectedPrefix = args[0]; + String expectedSuffix = args[1]; RelationalPersistentProperty property = requiredPersistentEntity.getRequiredPersistentProperty(name); - if (!prefix.isEmpty()) { + if (!expectedPrefix.isEmpty() || !expectedSuffix.isEmpty()) { softly.assertThat(property.isEmbedded()) // .describedAs(name + " is embedded") // .isTrue(); @@ -113,13 +114,19 @@ void detectsEmbeddedEntity() { softly.assertThat(property.getEmbeddedPrefix()) // .describedAs(name + " prefix") // - .isEqualTo(prefix); + .isEqualTo(expectedPrefix); + + softly.assertThat(property.getEmbeddedSuffix()) // + .describedAs(name + " suffix") // + .isEqualTo(expectedSuffix); }; - checkEmbedded.accept("someList", ""); - checkEmbedded.accept("id", ""); - checkEmbedded.accept("embeddableEntity", ""); - checkEmbedded.accept("prefixedEmbeddableEntity", "prefix"); + checkEmbedded.accept("someList", new String[]{"", ""}); + checkEmbedded.accept("id", new String[]{"", ""}); + checkEmbedded.accept("embeddableEntity", new String[]{"", ""}); + + checkEmbedded.accept("prefixedEmbeddableEntity", new String[]{"prefix", ""}); + checkEmbedded.accept("suffixedEmbeddableEntity", new String[]{"", "_suffixed"}); softly.assertAll(); } @@ -190,6 +197,18 @@ void determineSequenceNameWithSchemaSpecified() { .isEqualTo(SqlIdentifier.from(SqlIdentifier.quoted("public"), SqlIdentifier.quoted("my_seq"))); } + @Test // GH-2303 + void detectsEmbeddedEntitySuffix() { + final RelationalPersistentEntity requiredPersistentEntity = context + .getRequiredPersistentEntity(DummyEntity.class); + + RelationalPersistentProperty property = requiredPersistentEntity + .getRequiredPersistentProperty("suffixedEmbeddableEntity"); + + assertThat(property.isEmbedded()).isTrue(); + assertThat(property.getEmbeddedSuffix()).isEqualTo("_suffixed"); + } + @SuppressWarnings("unused") static class DummyEntity { @@ -230,6 +249,8 @@ static class DummyEntity { // DATAJDBC-111 private @Embedded(onEmpty = OnEmpty.USE_NULL, prefix = "prefix") EmbeddableEntity prefixedEmbeddableEntity; + private @Embedded(onEmpty = OnEmpty.USE_NULL, suffix = "_suffixed") EmbeddableEntity suffixedEmbeddableEntity; + public DummyEntity(Long id, SomeEnum someEnum, LocalDateTime localDateTime, ZonedDateTime zonedDateTime, List listOfString, String[] arrayOfString, List listOfEntity, OtherEntity[] arrayOfEntity) { 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 46e943f69c..5e65c34b71 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 @@ -90,7 +90,7 @@ public void rootAggregatePathsGetCached() { assertThat((Object) one).isSameAs(two); } - @Test // GH-1586 + @Test // GH-1586, GH-2303 void correctlyCascadesPrefix() { RelationalPersistentEntity entity = context.getRequiredPersistentEntity(WithEmbedded.class); @@ -102,8 +102,10 @@ void correctlyCascadesPrefix() { RelationalPersistentProperty name = childEntity.getRequiredPersistentProperty("name"); assertThat(parent.getEmbeddedPrefix()).isEqualTo("prnt_"); + assertThat(parent.getEmbeddedSuffix()).isEqualTo("_prnt"); assertThat(child.getEmbeddedPrefix()).isEqualTo("prnt_chld_"); - assertThat(name.getColumnName()).isEqualTo(SqlIdentifier.quoted("PRNT_CHLD_NAME")); + assertThat(child.getEmbeddedSuffix()).isEqualTo("_chld_prnt"); + assertThat(name.getColumnName()).isEqualTo(SqlIdentifier.quoted("PRNT_CHLD_NAME_CHLD_PRNT")); } @Test // GH-1657 @@ -125,7 +127,7 @@ static class EntityWithUuid { } static class WithEmbedded { - @Embedded.Empty(prefix = "prnt_") Parent parent; + @Embedded.Empty(prefix = "prnt_", suffix = "_prnt") Parent parent; } static class WithEmbeddedId { @@ -138,7 +140,7 @@ private record CompositeId(int a, int b) { static class Parent { - @Embedded.Empty(prefix = "chld_") Child child; + @Embedded.Empty(prefix = "chld_", suffix = "_chld") Child child; } static class Child {