From 40446f9ca936f2d56491d8cd7d851cc9caac192e Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 1 Sep 2022 15:02:55 +0200 Subject: [PATCH] The back reference generation is now configurable. The default version is the behavior that existed so far: The back reference is the table name as generated by the `NamingStrategy` without taking `@Table` annotations into account. The new alternative is to take `@Table` into account. The behavior can be configured by setting the `foreignKeyNaming` property on the `RelationalMappingContext`. Closes #1161 Closes #1147 Original pull request: #1324. --- .../convert/DefaultDataAccessStrategy.java | 4 +- .../data/jdbc/core/convert/SqlContext.java | 2 +- .../data/jdbc/core/convert/SqlGenerator.java | 22 +++++- .../jdbc/repository/query/SqlContext.java | 2 +- .../core/convert/SqlGeneratorUnitTests.java | 67 ++++++++++++++++-- .../DefaultReactiveDataAccessStrategy.java | 2 +- .../data/r2dbc/core/R2dbcEntityTemplate.java | 12 ++-- .../core/mapping/CachingNamingStrategy.java | 5 ++ .../core/mapping/DefaultNamingStrategy.java | 69 +++++++++++++++++++ .../core/mapping/ForeignKeyNaming.java | 33 +++++++++ .../core/mapping/NamingStrategy.java | 15 +++- .../mapping/RelationalMappingContext.java | 18 +++++ .../mapping/RelationalPersistentEntity.java | 21 ++++++ .../RelationalPersistentEntityImpl.java | 16 ++++- .../query/SimpleRelationalEntityMetadata.java | 2 +- .../MappingRelationalEntityInformation.java | 2 +- ...lationalPersistentEntityImplUnitTests.java | 34 ++++++--- src/main/asciidoc/jdbc.adoc | 26 +++++-- 18 files changed, 313 insertions(+), 39 deletions(-) create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultNamingStrategy.java create mode 100644 spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ForeignKeyNaming.java 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 76bff41d..e4c79529 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 @@ -298,10 +298,10 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { Assert.notNull(propertyPath, "propertyPath must not be null"); PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, propertyPath); - Class actualType = path.getActualType(); + String findAllByProperty = sql(actualType) // - .getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered()); + .getFindAllByProperty(identifier, propertyPath); RowMapper rowMapper = path.isMap() ? this.getMapEntityRowMapper(path, identifier) : this.getEntityRowMapper(path, 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 5a91abb3..b0326aec 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 @@ -37,7 +37,7 @@ class SqlContext { SqlContext(RelationalPersistentEntity entity) { this.entity = entity; - this.table = Table.create(entity.getTableName()); + this.table = Table.create(entity.getFullTableName()); } Column getIdColumn() { 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 4a82a7f7..ec7027ec 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 @@ -200,6 +200,26 @@ class SqlGenerator { return render(selectBuilder(Collections.emptyList(), pageable.getSort(), pageable).build()); } + /** + * Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships. + * Results are limited to those rows referencing some parent entity. This is used to select values for a complex + * property ({@link Set}, {@link Map} ...) based on a referencing entity. + * + * @param parentIdentifier name of the column of the FK back to the referencing entity. + * @param propertyPath used to determine if the property is ordered and if there is a key column. + * @return a SQL String. + */ + String getFindAllByProperty(Identifier parentIdentifier, + PersistentPropertyPath propertyPath) { + + Assert.notNull(parentIdentifier, "identifier must not be null"); + Assert.notNull(propertyPath, "propertyPath must not be null"); + + PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(mappingContext, propertyPath); + + return getFindAllByProperty(parentIdentifier, path.getQualifierColumn(), path.isOrdered()); + } + /** * Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships. * Results are limited to those rows referencing some other entity using the column specified by @@ -915,7 +935,7 @@ class SqlGenerator { private SelectBuilder.SelectOrdered applyQueryOnSelect(Query query, MapSqlParameterSource parameterSource, SelectBuilder.SelectWhere selectBuilder) { - Table table = Table.create(this.entity.getTableName()); + Table table = Table.create(this.entity.getFullTableName()); SelectBuilder.SelectOrdered selectOrdered = query // .getCriteria() // 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 e500ab75..b9559cec 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 @@ -38,7 +38,7 @@ class SqlContext { SqlContext(RelationalPersistentEntity entity) { this.entity = entity; - this.table = Table.create(entity.getTableName()); + this.table = Table.create(entity.getFullTableName()); } Column getIdColumn() { 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 5b169a9f..653f456f 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 @@ -18,6 +18,7 @@ package org.springframework.data.jdbc.core.convert; import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.SoftAssertions.*; +import static org.springframework.data.relational.core.mapping.ForeignKeyNaming.*; import static org.springframework.data.relational.core.sql.SqlIdentifier.*; import java.util.Map; @@ -41,6 +42,7 @@ 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.Column; +import org.springframework.data.relational.core.mapping.DefaultNamingStrategy; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalMappingContext; @@ -763,7 +765,7 @@ class SqlGeneratorUnitTests { @Test // GH-1192 void selectByQueryValidTest() { - SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class); + SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class); DummyEntity probe = new DummyEntity(); probe.name = "Diego"; @@ -862,6 +864,50 @@ class SqlGeneratorUnitTests { .containsOnly(entry("x_name", probe.name)); } + @Test // GH-1161 + void backReferenceShouldConsiderRenamedParent() { + + context.setForeignKeyNaming(APPLY_RENAMING); + + String sql = sqlGenerator.createDeleteInByPath(getPath("ref", RenamedDummy.class)); + + assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE referenced_entity.renamed IN (:ids)"); + + } + + @Test // GH-1161 + void backReferenceShouldIgnoreRenamedParent() { + + context.setForeignKeyNaming(IGNORE_RENAMING); + + String sql = sqlGenerator.createDeleteInByPath(getPath("ref", RenamedDummy.class)); + + assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE referenced_entity.renamed_dummy IN (:ids)"); + } + + @Test // GH-1161 + void keyColumnShouldConsiderRenamedParent() { + + context.setForeignKeyNaming(APPLY_RENAMING); + SqlGenerator sqlGenerator = createSqlGenerator(ReferencedEntity.class); + String sql = sqlGenerator.getFindAllByProperty(Identifier.of(unquoted("parentId"), 23, RenamedDummy.class), getPath("ref", RenamedDummy.class)); + + assertThat(sql) + .contains("referenced_entity.renamed_key AS renamed_key", "WHERE referenced_entity.parentId"); + } + + @Test // GH-1161 + void keyColumnShouldIgnoreRenamedParent() { + + context.setForeignKeyNaming(IGNORE_RENAMING); + SqlGenerator sqlGenerator = createSqlGenerator(ReferencedEntity.class); + String sql = sqlGenerator.getFindAllByProperty(Identifier.of(unquoted("parentId"), 23, RenamedDummy.class), getPath("ref", RenamedDummy.class)); + + assertThat(sql) + .contains("referenced_entity.renamed_dummy_key AS renamed_dummy_key", "WHERE referenced_entity.parentId"); + } + + @Nullable private SqlIdentifier getAlias(Object maybeAliased) { @@ -885,8 +931,7 @@ class SqlGeneratorUnitTests { @SuppressWarnings("unused") static class DummyEntity { - @Column("id1") - @Id Long id; + @Column("id1") @Id Long id; String name; ReferencedEntity ref; Set elements; @@ -895,6 +940,15 @@ class SqlGeneratorUnitTests { Map mappedReference; } + @SuppressWarnings("unused") + @org.springframework.data.relational.core.mapping.Table("renamed") + static class RenamedDummy { + + @Id Long id; + String name; + Map ref; + } + @SuppressWarnings("unused") static class VersionedEntity extends DummyEntity { @Version Integer version; @@ -936,11 +990,11 @@ class SqlGeneratorUnitTests { String name; } - private static class PrefixingNamingStrategy implements NamingStrategy { + private static class PrefixingNamingStrategy extends DefaultNamingStrategy { @Override public String getColumnName(RelationalPersistentProperty property) { - return "x_" + NamingStrategy.super.getColumnName(property); + return "x_" + super.getColumnName(property); } } @@ -964,8 +1018,7 @@ class SqlGeneratorUnitTests { // these column names behave like single double quote in the name since the get quoted and then doubling the double // quote escapes it. - @Id - @Column("test\"\"_@id") Long id; + @Id @Column("test\"\"_@id") Long id; @Column("test\"\"_@123") String name; } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java index 363ff19a..85fad494 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java @@ -277,7 +277,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra @Override public SqlIdentifier getTableName(Class type) { - return getRequiredPersistentEntity(type).getTableName(); + return getRequiredPersistentEntity(type).getFullTableName(); } @Override diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java index 5a72ab00..5444afa3 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java @@ -465,7 +465,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw Assert.notNull(entity, "Entity must not be null"); - return doInsert(entity, getRequiredEntity(entity).getTableName()); + return doInsert(entity, getRequiredEntity(entity).getFullTableName()); } Mono doInsert(T entity, SqlIdentifier tableName) { @@ -564,7 +564,7 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw Assert.notNull(entity, "Entity must not be null"); - return doUpdate(entity, getRequiredEntity(entity).getTableName()); + return doUpdate(entity, getRequiredEntity(entity).getFullTableName()); } private Mono doUpdate(T entity, SqlIdentifier tableName) { @@ -644,13 +644,13 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw private String formatOptimisticLockingExceptionMessage(T entity, RelationalPersistentEntity persistentEntity) { return String.format("Failed to update table [%s]; Version does not match for row with Id [%s]", - persistentEntity.getTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier()); + persistentEntity.getFullTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier()); } private String formatTransientEntityExceptionMessage(T entity, RelationalPersistentEntity persistentEntity) { return String.format("Failed to update table [%s]; Row with Id [%s] does not exist", - persistentEntity.getTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier()); + persistentEntity.getFullTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier()); } @SuppressWarnings("unchecked") @@ -744,14 +744,14 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw } SqlIdentifier getTableName(Class entityClass) { - return getRequiredEntity(entityClass).getTableName(); + return getRequiredEntity(entityClass).getFullTableName(); } SqlIdentifier getTableNameOrEmpty(Class entityClass) { RelationalPersistentEntity entity = this.mappingContext.getPersistentEntity(entityClass); - return entity != null ? entity.getTableName() : SqlIdentifier.EMPTY; + return entity != null ? entity.getFullTableName() : SqlIdentifier.EMPTY; } private RelationalPersistentEntity getRequiredEntity(Class entityClass) { 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 3a9837ea..4cf7d955 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 @@ -82,4 +82,9 @@ class CachingNamingStrategy implements NamingStrategy { public String getColumnName(RelationalPersistentProperty property) { return columnNames.computeIfAbsent(property, delegate::getColumnName); } + + @Override + public void setForeignKeyNaming(ForeignKeyNaming foreignKeyNaming) { + delegate.setForeignKeyNaming(foreignKeyNaming); + } } 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 new file mode 100644 index 00000000..4657a071 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultNamingStrategy.java @@ -0,0 +1,69 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.relational.core.mapping; + +import org.jetbrains.annotations.NotNull; +import org.springframework.util.Assert; + +/** + * The default naming strategy used by Spring Data Relational. Names are in SNAKE_CASE. + * + * @author Jens Schauder + * @since 2.4 + */ +public class DefaultNamingStrategy implements NamingStrategy { + + /** + * Since in most cases it doesn't make sense to have more than one {@link NamingStrategy} use of this instance is + * recommended. + */ + public static NamingStrategy INSTANCE = new DefaultNamingStrategy(); + + private ForeignKeyNaming foreignKeyNaming = ForeignKeyNaming.IGNORE_RENAMING; + + @Override + public void setForeignKeyNaming(ForeignKeyNaming foreignKeyNaming) { + + Assert.notNull(foreignKeyNaming, "foreignKeyNaming must not be null"); + + this.foreignKeyNaming = foreignKeyNaming; + } + + @Override + public String getReverseColumnName(RelationalPersistentProperty property) { + + return getColumnNameReferencing(property.getOwner()); + } + + @Override + public String getReverseColumnName(PersistentPropertyPathExtension path) { + + RelationalPersistentEntity leafEntity = path.getIdDefiningParentPath().getLeafEntity(); + + return getColumnNameReferencing(leafEntity); + } + + private String getColumnNameReferencing(RelationalPersistentEntity leafEntity) { + + Assert.state(leafEntity != null, "Leaf Entity must not be null."); + + if (foreignKeyNaming == ForeignKeyNaming.IGNORE_RENAMING) { + return getTableName(leafEntity.getType()); + } + + return leafEntity.getSimpleTableName().getReference(); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ForeignKeyNaming.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ForeignKeyNaming.java new file mode 100644 index 00000000..e5f98123 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ForeignKeyNaming.java @@ -0,0 +1,33 @@ +/* + * Copyright 2022 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; + +/** + * Enum for determining how the names of back references should get generated. + * + * @author Jens Schauder + * @since 2.4 + */ +public enum ForeignKeyNaming { + /** + * This strategy takes names specified via {@link Table} annotation into account. + */ + APPLY_RENAMING, + /** + * This strategy does not take names specified via {@link Table} annotation into account. + */ + IGNORE_RENAMING +} 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 bc1fdaea..a19a988b 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 @@ -38,8 +38,11 @@ public interface NamingStrategy { * Empty implementation of the interface utilizing only the default implementation. *

* Using this avoids creating essentially the same class over and over again. + * + * @deprecated use {@link DefaultNamingStrategy#INSTANCE} instead. */ - NamingStrategy INSTANCE = new NamingStrategy() {}; + @Deprecated(since = "2.4") + NamingStrategy INSTANCE = DefaultNamingStrategy.INSTANCE; /** * Defaults to no schema. @@ -82,7 +85,7 @@ public interface NamingStrategy { Assert.notNull(property, "Property must not be null"); - return property.getOwner().getTableName().getReference(IdentifierProcessing.NONE); + return property.getOwner().getSimpleTableName().getReference(IdentifierProcessing.NONE); } default String getReverseColumnName(PersistentPropertyPathExtension path) { @@ -102,4 +105,12 @@ public interface NamingStrategy { return getReverseColumnName(property) + "_key"; } + + /** + * Set the {@link ForeignKeyNaming} strategy used in this {@link NamingStrategy}. + * + * @param foreignKeyNaming the ForeignKeyNaming strategy to be used. Must not be {@literal null}. + * @since 2.4 + */ + default void setForeignKeyNaming(ForeignKeyNaming foreignKeyNaming) {} } 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 11a43248..7d8d9716 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 @@ -36,6 +36,7 @@ public class RelationalMappingContext private final NamingStrategy namingStrategy; private boolean forceQuote = true; + private ForeignKeyNaming foreignKeyNaming = ForeignKeyNaming.IGNORE_RENAMING; /** * Creates a new {@link RelationalMappingContext}. @@ -53,6 +54,7 @@ public class RelationalMappingContext Assert.notNull(namingStrategy, "NamingStrategy must not be null"); + namingStrategy.setForeignKeyNaming(foreignKeyNaming); this.namingStrategy = new CachingNamingStrategy(namingStrategy); setSimpleTypeHolder(SimpleTypeHolder.DEFAULT); @@ -101,4 +103,20 @@ public class RelationalMappingContext public NamingStrategy getNamingStrategy() { return this.namingStrategy; } + + /** + * Sets the {@link ForeignKeyNaming} to be used by this mapping context. + * + * @param foreignKeyNaming must not be {@literal null}. + * @since 2.4 + */ + public void setForeignKeyNaming(ForeignKeyNaming foreignKeyNaming) { + + Assert.notNull(foreignKeyNaming, "foreignKeyNaming must not be null"); + + this.foreignKeyNaming = foreignKeyNaming; + if (namingStrategy != null) { + namingStrategy.setForeignKeyNaming(foreignKeyNaming); + } + } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java index 15b32347..f5e61976 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java @@ -31,13 +31,34 @@ public interface RelationalPersistentEntity extends MutablePersistentEntity extends BasicPersistentEntity explicitlySpecifiedTableName = tableName.get(); - final SqlIdentifier schemalessTableIdentifier = createDerivedSqlIdentifier(namingStrategy.getTableName(getType())); + SqlIdentifier schemalessTableIdentifier = createDerivedSqlIdentifier(namingStrategy.getTableName(getType())); if (schema == null) { return explicitlySpecifiedTableName.orElse(schemalessTableIdentifier); @@ -96,6 +101,15 @@ class RelationalPersistentEntityImpl extends BasicPersistentEntity explicitlySpecifiedTableName = tableName.get(); + SqlIdentifier schemalessTableIdentifier = createDerivedSqlIdentifier(namingStrategy.getTableName(getType())); + + return explicitlySpecifiedTableName.orElse(schemalessTableIdentifier); + } + /** * @return {@link SqlIdentifier} representing the current entity schema. If the schema is not specified, neither * explicitly, nor via {@link NamingStrategy}, then return {@link null} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/SimpleRelationalEntityMetadata.java b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/SimpleRelationalEntityMetadata.java index a633302a..c99bf5c8 100755 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/SimpleRelationalEntityMetadata.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/SimpleRelationalEntityMetadata.java @@ -50,7 +50,7 @@ public class SimpleRelationalEntityMetadata implements RelationalEntityMetada } public SqlIdentifier getTableName() { - return tableEntity.getTableName(); + return tableEntity.getFullTableName(); } public RelationalPersistentEntity getTableEntity() { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/support/MappingRelationalEntityInformation.java b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/support/MappingRelationalEntityInformation.java index 0af57a0d..5bd516f6 100755 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/support/MappingRelationalEntityInformation.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/support/MappingRelationalEntityInformation.java @@ -87,7 +87,7 @@ public class MappingRelationalEntityInformation extends PersistentEntityI } public SqlIdentifier getTableName() { - return customTableName == null ? entityMetadata.getTableName() : customTableName; + return customTableName == null ? entityMetadata.getFullTableName() : customTableName; } public String getIdAttribute() { diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntityImplUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntityImplUnitTests.java index 8008a3c9..b8293499 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntityImplUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntityImplUnitTests.java @@ -42,6 +42,8 @@ public class RelationalPersistentEntityImplUnitTests { RelationalPersistentEntity entity = mappingContext.getPersistentEntity(DummySubEntity.class); assertThat(entity.getTableName()).isEqualTo(quoted("dummy_sub_entity")); + assertThat(entity.getFullTableName()).isEqualTo(quoted("dummy_sub_entity")); + assertThat(entity.getSimpleTableName()).isEqualTo(quoted("dummy_sub_entity")); } @Test // DATAJDBC-294 @@ -58,6 +60,8 @@ public class RelationalPersistentEntityImplUnitTests { RelationalPersistentEntity entity = mappingContext.getPersistentEntity(DummyEntityWithEmptyAnnotation.class); assertThat(entity.getTableName()).isEqualTo(quoted("DUMMY_ENTITY_WITH_EMPTY_ANNOTATION")); + assertThat(entity.getFullTableName()).isEqualTo(quoted("DUMMY_ENTITY_WITH_EMPTY_ANNOTATION")); + assertThat(entity.getSimpleTableName()).isEqualTo(quoted("DUMMY_ENTITY_WITH_EMPTY_ANNOTATION")); } @Test // DATAJDBC-491 @@ -66,8 +70,16 @@ public class RelationalPersistentEntityImplUnitTests { mappingContext = new RelationalMappingContext(NamingStrategyWithSchema.INSTANCE); RelationalPersistentEntity entity = mappingContext.getPersistentEntity(DummyEntityWithEmptyAnnotation.class); + SqlIdentifier simpleExpected = quoted("DUMMY_ENTITY_WITH_EMPTY_ANNOTATION"); + SqlIdentifier fullExpected = SqlIdentifier.from(quoted("MY_SCHEMA"), simpleExpected); + assertThat(entity.getTableName()) - .isEqualTo(SqlIdentifier.from(quoted("MY_SCHEMA"), quoted("DUMMY_ENTITY_WITH_EMPTY_ANNOTATION"))); + .isEqualTo(fullExpected); + assertThat(entity.getFullTableName()) + .isEqualTo(fullExpected); + assertThat(entity.getSimpleTableName()) + .isEqualTo(simpleExpected); + assertThat(entity.getTableName().toSql(IdentifierProcessing.ANSI)) .isEqualTo("\"MY_SCHEMA\".\"DUMMY_ENTITY_WITH_EMPTY_ANNOTATION\""); } @@ -76,21 +88,25 @@ public class RelationalPersistentEntityImplUnitTests { void testRelationalPersistentEntitySchemaNameChoice() { mappingContext = new RelationalMappingContext(NamingStrategyWithSchema.INSTANCE); - RelationalPersistentEntity persistentEntity = mappingContext.getPersistentEntity(EntityWithSchemaAndName.class); + RelationalPersistentEntity entity = mappingContext.getPersistentEntity(EntityWithSchemaAndName.class); - SqlIdentifier tableName = persistentEntity.getTableName(); - - assertThat(tableName).isEqualTo(SqlIdentifier.from(SqlIdentifier.quoted("DART_VADER"), quoted("I_AM_THE_SENATE"))); + SqlIdentifier simpleExpected = quoted("I_AM_THE_SENATE"); + SqlIdentifier expected = SqlIdentifier.from(quoted("DART_VADER"), simpleExpected); + assertThat(entity.getTableName()).isEqualTo(expected); + assertThat(entity.getFullTableName()).isEqualTo(expected); + assertThat(entity.getSimpleTableName()).isEqualTo(simpleExpected); } @Test // GH-1099 void specifiedSchemaGetsCombinedWithNameFromNamingStrategy() { - RelationalPersistentEntity persistentEntity = mappingContext.getPersistentEntity(EntityWithSchema.class); + RelationalPersistentEntity entity = mappingContext.getPersistentEntity(EntityWithSchema.class); - SqlIdentifier tableName = persistentEntity.getTableName(); - - assertThat(tableName).isEqualTo(SqlIdentifier.from(quoted("ANAKYN_SKYWALKER"), quoted("ENTITY_WITH_SCHEMA"))); + SqlIdentifier simpleExpected = quoted("ENTITY_WITH_SCHEMA"); + SqlIdentifier expected = SqlIdentifier.from(quoted("ANAKYN_SKYWALKER"), simpleExpected); + assertThat(entity.getTableName()).isEqualTo(expected); + assertThat(entity.getFullTableName()).isEqualTo(expected); + assertThat(entity.getSimpleTableName()).isEqualTo(simpleExpected); } @Table(schema = "ANAKYN_SKYWALKER") diff --git a/src/main/asciidoc/jdbc.adoc b/src/main/asciidoc/jdbc.adoc index 4b900c62..123d607c 100644 --- a/src/main/asciidoc/jdbc.adoc +++ b/src/main/asciidoc/jdbc.adoc @@ -216,22 +216,23 @@ The properties of the following types are currently supported: * References to other entities. They are considered a one-to-one relationship, or an embedded type. It is optional for one-to-one relationship entities to have an `id` attribute. -The table of the referenced entity is expected to have an additional column named the same as the table of the referencing entity. -You can change this name by implementing `NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path)`. +The table of the referenced entity is expected to have an additional column with a name based on the referencing entity see <>. Embedded entities do not need an `id`. If one is present it gets ignored. * `Set` is considered a one-to-many relationship. -The table of the referenced entity is expected to have an additional column named the same as the table of the referencing entity. -You can change this name by implementing `NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path)`. +The table of the referenced entity is expected to have an additional column with a name based on the referencing entity see <>. * `Map` is considered a qualified one-to-many relationship. -The table of the referenced entity is expected to have two additional columns: One named the same as the table of the referencing entity for the foreign key and one with the same name and an additional `_key` suffix for the map key. +The table of the referenced entity is expected to have two additional columns: One named based on the referencing entity for the foreign key (see <>) and one with the same name and an additional `_key` suffix for the map key. You can change this behavior by implementing `NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path)` and `NamingStrategy.getKeyColumn(RelationalPersistentProperty property)`, respectively. Alternatively you may annotate the attribute with `@MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name")` * `List` is mapped as a `Map`. +[[jdbc.entity-persistence.types.referenced-entities]] +==== Referenced Entities + The handling of referenced entities is limited. This is based on the idea of aggregate roots as described above. If you reference another entity, that entity is, by definition, part of your aggregate. @@ -240,10 +241,23 @@ This also means references are 1-1 or 1-n, but not n-1 or n-m. If you have n-1 or n-m references, you are, by definition, dealing with two separate aggregates. References between those may be encoded as simple `id` values, which map properly with Spring Data JDBC. -A better way to encode these is to make them instances of `AggregateReference`. +A better way to encode these, is to make them instances of `AggregateReference`. An `AggregateReference` is a wrapper around an id value which marks that value as a reference to a different aggregate. Also, the type of that aggregate is encoded in a type parameter. +[[jdbc.entity-persistence.types.backrefs]] +==== Back References + +All references in an aggregate result in a foreign key relationship in the opposite direction in the database. +By default, the name of the foreign key column is the table name of the referencing entity, ignoring any table annotations. + +Alternatively you may choose to have them named by the actual table name of the referencing entity. +You activate this behaviour by calling `setForeignKeyNaming(ForeignKeyNaming.APPLY_RENAMING)` on the `RelationalMappingContext`. + +For `List` and `Map` references an additional column is required for holding the list index or map key. It is based on the foreign key column with an additional `_KEY` suffix. + +If you want a completely different way of naming these back references you may implement `NamingStrategy.getReverseColumnName(PersistentPropertyPathExtension path)` in a way that fits your needs. + .Declaring and setting an `AggregateReference` ====