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.
This commit is contained in:
Jens Schauder
2022-09-01 15:02:55 +02:00
committed by Mark Paluch
parent 15796b88fe
commit 40446f9ca9
18 changed files with 313 additions and 39 deletions

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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<? extends RelationalPersistentProperty> 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() //

View File

@@ -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() {

View File

@@ -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<Element> elements;
@@ -895,6 +940,15 @@ class SqlGeneratorUnitTests {
Map<Long, ReferencedEntity> mappedReference;
}
@SuppressWarnings("unused")
@org.springframework.data.relational.core.mapping.Table("renamed")
static class RenamedDummy {
@Id Long id;
String name;
Map<String, ReferencedEntity> 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;
}

View File

@@ -277,7 +277,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra
@Override
public SqlIdentifier getTableName(Class<?> type) {
return getRequiredPersistentEntity(type).getTableName();
return getRequiredPersistentEntity(type).getFullTableName();
}
@Override

View File

@@ -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());
}
<T> Mono<T> 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 <T> Mono<T> doUpdate(T entity, SqlIdentifier tableName) {
@@ -644,13 +644,13 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
private <T> String formatOptimisticLockingExceptionMessage(T entity, RelationalPersistentEntity<T> 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 <T> String formatTransientEntityExceptionMessage(T entity, RelationalPersistentEntity<T> 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) {

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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
}

View File

@@ -38,8 +38,11 @@ public interface NamingStrategy {
* Empty implementation of the interface utilizing only the default implementation.
* <p>
* 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) {}
}

View File

@@ -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);
}
}
}

View File

@@ -31,13 +31,34 @@ public interface RelationalPersistentEntity<T> extends MutablePersistentEntity<T
* Returns the name of the table backing the given entity.
*
* @return the table name.
* @deprecated Use either {@link #getFullTableName()} or {@link #getSimpleTableName()}
*/
@Deprecated(since = "2.4")
SqlIdentifier getTableName();
/**
* Returns the name of the table backing the given entity, including the schema.
*
* @return the table name including the schema if there is any specified.
* @since 2.4
*/
default SqlIdentifier getFullTableName() {
return getTableName();
}
/**
* Returns the name of the table backing the given entity, without any schema.
*
* @return the table name.
* @since 2.4
*/
SqlIdentifier getSimpleTableName();
/**
* Returns the column representing the identifier.
*
* @return will never be {@literal null}.
*/
SqlIdentifier getIdColumn();
}

View File

@@ -81,11 +81,16 @@ class RelationalPersistentEntityImpl<T> extends BasicPersistentEntity<T, Relatio
@Override
public SqlIdentifier getTableName() {
return getFullTableName();
}
@Override
public SqlIdentifier getFullTableName() {
SqlIdentifier schema = determineCurrentEntitySchema();
Optional<SqlIdentifier> 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<T> extends BasicPersistentEntity<T, Relatio
.orElse(SqlIdentifier.from(schema, schemalessTableIdentifier));
}
@Override
public SqlIdentifier getSimpleTableName() {
Optional<SqlIdentifier> 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}

View File

@@ -50,7 +50,7 @@ public class SimpleRelationalEntityMetadata<T> implements RelationalEntityMetada
}
public SqlIdentifier getTableName() {
return tableEntity.getTableName();
return tableEntity.getFullTableName();
}
public RelationalPersistentEntity<?> getTableEntity() {

View File

@@ -87,7 +87,7 @@ public class MappingRelationalEntityInformation<T, ID> extends PersistentEntityI
}
public SqlIdentifier getTableName() {
return customTableName == null ? entityMetadata.getTableName() : customTableName;
return customTableName == null ? entityMetadata.getFullTableName() : customTableName;
}
public String getIdAttribute() {

View File

@@ -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")

View File

@@ -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 <<jdbc.entity-persistence.types.backrefs>>.
Embedded entities do not need an `id`.
If one is present it gets ignored.
* `Set<some entity>` 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 <<jdbc.entity-persistence.types.backrefs>>.
* `Map<simple type, some entity>` 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 <<jdbc.entity-persistence.types.backrefs>>) 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<some entity>` is mapped as a `Map<Integer, some entity>`.
[[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`
====