Skip to content

Commit 40446f9

Browse files
schaudermp911de
authored andcommitted
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.
1 parent 15796b8 commit 40446f9

File tree

18 files changed

+313
-39
lines changed

18 files changed

+313
-39
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,10 @@ public Iterable<Object> findAllByPath(Identifier identifier,
298298
Assert.notNull(propertyPath, "propertyPath must not be null");
299299

300300
PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(context, propertyPath);
301-
302301
Class<?> actualType = path.getActualType();
302+
303303
String findAllByProperty = sql(actualType) //
304-
.getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered());
304+
.getFindAllByProperty(identifier, propertyPath);
305305

306306
RowMapper<?> rowMapper = path.isMap() ? this.getMapEntityRowMapper(path, identifier)
307307
: this.getEntityRowMapper(path, identifier);

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class SqlContext {
3737
SqlContext(RelationalPersistentEntity<?> entity) {
3838

3939
this.entity = entity;
40-
this.table = Table.create(entity.getTableName());
40+
this.table = Table.create(entity.getFullTableName());
4141
}
4242

4343
Column getIdColumn() {

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,26 @@ String getFindAll(Pageable pageable) {
200200
return render(selectBuilder(Collections.emptyList(), pageable.getSort(), pageable).build());
201201
}
202202

203+
/**
204+
* Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships.
205+
* Results are limited to those rows referencing some parent entity. This is used to select values for a complex
206+
* property ({@link Set}, {@link Map} ...) based on a referencing entity.
207+
*
208+
* @param parentIdentifier name of the column of the FK back to the referencing entity.
209+
* @param propertyPath used to determine if the property is ordered and if there is a key column.
210+
* @return a SQL String.
211+
*/
212+
String getFindAllByProperty(Identifier parentIdentifier,
213+
PersistentPropertyPath<? extends RelationalPersistentProperty> propertyPath) {
214+
215+
Assert.notNull(parentIdentifier, "identifier must not be null");
216+
Assert.notNull(propertyPath, "propertyPath must not be null");
217+
218+
PersistentPropertyPathExtension path = new PersistentPropertyPathExtension(mappingContext, propertyPath);
219+
220+
return getFindAllByProperty(parentIdentifier, path.getQualifierColumn(), path.isOrdered());
221+
}
222+
203223
/**
204224
* Returns a query for selecting all simple properties of an entity, including those for one-to-one relationships.
205225
* Results are limited to those rows referencing some other entity using the column specified by
@@ -915,7 +935,7 @@ private SelectBuilder.SelectJoin getSelectCountWithExpression(Expression... coun
915935
private SelectBuilder.SelectOrdered applyQueryOnSelect(Query query, MapSqlParameterSource parameterSource,
916936
SelectBuilder.SelectWhere selectBuilder) {
917937

918-
Table table = Table.create(this.entity.getTableName());
938+
Table table = Table.create(this.entity.getFullTableName());
919939

920940
SelectBuilder.SelectOrdered selectOrdered = query //
921941
.getCriteria() //

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class SqlContext {
3838
SqlContext(RelationalPersistentEntity<?> entity) {
3939

4040
this.entity = entity;
41-
this.table = Table.create(entity.getTableName());
41+
this.table = Table.create(entity.getFullTableName());
4242
}
4343

4444
Column getIdColumn() {

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static java.util.Collections.*;
1919
import static org.assertj.core.api.Assertions.*;
2020
import static org.assertj.core.api.SoftAssertions.*;
21+
import static org.springframework.data.relational.core.mapping.ForeignKeyNaming.*;
2122
import static org.springframework.data.relational.core.sql.SqlIdentifier.*;
2223

2324
import java.util.Map;
@@ -41,6 +42,7 @@
4142
import org.springframework.data.relational.core.dialect.PostgresDialect;
4243
import org.springframework.data.relational.core.dialect.SqlServerDialect;
4344
import org.springframework.data.relational.core.mapping.Column;
45+
import org.springframework.data.relational.core.mapping.DefaultNamingStrategy;
4446
import org.springframework.data.relational.core.mapping.NamingStrategy;
4547
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
4648
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -763,7 +765,7 @@ void columnForReferencedEntityWithoutId() {
763765
@Test // GH-1192
764766
void selectByQueryValidTest() {
765767

766-
SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class);
768+
SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class);
767769

768770
DummyEntity probe = new DummyEntity();
769771
probe.name = "Diego";
@@ -862,6 +864,50 @@ void selectByQueryPaginationValidTest() {
862864
.containsOnly(entry("x_name", probe.name));
863865
}
864866

867+
@Test // GH-1161
868+
void backReferenceShouldConsiderRenamedParent() {
869+
870+
context.setForeignKeyNaming(APPLY_RENAMING);
871+
872+
String sql = sqlGenerator.createDeleteInByPath(getPath("ref", RenamedDummy.class));
873+
874+
assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE referenced_entity.renamed IN (:ids)");
875+
876+
}
877+
878+
@Test // GH-1161
879+
void backReferenceShouldIgnoreRenamedParent() {
880+
881+
context.setForeignKeyNaming(IGNORE_RENAMING);
882+
883+
String sql = sqlGenerator.createDeleteInByPath(getPath("ref", RenamedDummy.class));
884+
885+
assertThat(sql).isEqualTo("DELETE FROM referenced_entity WHERE referenced_entity.renamed_dummy IN (:ids)");
886+
}
887+
888+
@Test // GH-1161
889+
void keyColumnShouldConsiderRenamedParent() {
890+
891+
context.setForeignKeyNaming(APPLY_RENAMING);
892+
SqlGenerator sqlGenerator = createSqlGenerator(ReferencedEntity.class);
893+
String sql = sqlGenerator.getFindAllByProperty(Identifier.of(unquoted("parentId"), 23, RenamedDummy.class), getPath("ref", RenamedDummy.class));
894+
895+
assertThat(sql)
896+
.contains("referenced_entity.renamed_key AS renamed_key", "WHERE referenced_entity.parentId");
897+
}
898+
899+
@Test // GH-1161
900+
void keyColumnShouldIgnoreRenamedParent() {
901+
902+
context.setForeignKeyNaming(IGNORE_RENAMING);
903+
SqlGenerator sqlGenerator = createSqlGenerator(ReferencedEntity.class);
904+
String sql = sqlGenerator.getFindAllByProperty(Identifier.of(unquoted("parentId"), 23, RenamedDummy.class), getPath("ref", RenamedDummy.class));
905+
906+
assertThat(sql)
907+
.contains("referenced_entity.renamed_dummy_key AS renamed_dummy_key", "WHERE referenced_entity.parentId");
908+
}
909+
910+
865911
@Nullable
866912
private SqlIdentifier getAlias(Object maybeAliased) {
867913

@@ -885,8 +931,7 @@ private PersistentPropertyPath<RelationalPersistentProperty> getPath(String path
885931
@SuppressWarnings("unused")
886932
static class DummyEntity {
887933

888-
@Column("id1")
889-
@Id Long id;
934+
@Column("id1") @Id Long id;
890935
String name;
891936
ReferencedEntity ref;
892937
Set<Element> elements;
@@ -895,6 +940,15 @@ static class DummyEntity {
895940
Map<Long, ReferencedEntity> mappedReference;
896941
}
897942

943+
@SuppressWarnings("unused")
944+
@org.springframework.data.relational.core.mapping.Table("renamed")
945+
static class RenamedDummy {
946+
947+
@Id Long id;
948+
String name;
949+
Map<String, ReferencedEntity> ref;
950+
}
951+
898952
@SuppressWarnings("unused")
899953
static class VersionedEntity extends DummyEntity {
900954
@Version Integer version;
@@ -936,11 +990,11 @@ static class OtherAggregate {
936990
String name;
937991
}
938992

939-
private static class PrefixingNamingStrategy implements NamingStrategy {
993+
private static class PrefixingNamingStrategy extends DefaultNamingStrategy {
940994

941995
@Override
942996
public String getColumnName(RelationalPersistentProperty property) {
943-
return "x_" + NamingStrategy.super.getColumnName(property);
997+
return "x_" + super.getColumnName(property);
944998
}
945999

9461000
}
@@ -964,8 +1018,7 @@ static class EntityWithQuotedColumnName {
9641018

9651019
// these column names behave like single double quote in the name since the get quoted and then doubling the double
9661020
// quote escapes it.
967-
@Id
968-
@Column("test\"\"_@id") Long id;
1021+
@Id @Column("test\"\"_@id") Long id;
9691022
@Column("test\"\"_@123") String name;
9701023
}
9711024

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public PreparedOperation<?> processNamedParameters(String query, NamedParameterP
277277

278278
@Override
279279
public SqlIdentifier getTableName(Class<?> type) {
280-
return getRequiredPersistentEntity(type).getTableName();
280+
return getRequiredPersistentEntity(type).getFullTableName();
281281
}
282282

283283
@Override

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ public <T> Mono<T> insert(T entity) throws DataAccessException {
465465

466466
Assert.notNull(entity, "Entity must not be null");
467467

468-
return doInsert(entity, getRequiredEntity(entity).getTableName());
468+
return doInsert(entity, getRequiredEntity(entity).getFullTableName());
469469
}
470470

471471
<T> Mono<T> doInsert(T entity, SqlIdentifier tableName) {
@@ -564,7 +564,7 @@ public <T> Mono<T> update(T entity) throws DataAccessException {
564564

565565
Assert.notNull(entity, "Entity must not be null");
566566

567-
return doUpdate(entity, getRequiredEntity(entity).getTableName());
567+
return doUpdate(entity, getRequiredEntity(entity).getFullTableName());
568568
}
569569

570570
private <T> Mono<T> doUpdate(T entity, SqlIdentifier tableName) {
@@ -644,13 +644,13 @@ private <T> Mono<T> doUpdate(T entity, SqlIdentifier tableName, RelationalPersis
644644
private <T> String formatOptimisticLockingExceptionMessage(T entity, RelationalPersistentEntity<T> persistentEntity) {
645645

646646
return String.format("Failed to update table [%s]; Version does not match for row with Id [%s]",
647-
persistentEntity.getTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier());
647+
persistentEntity.getFullTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier());
648648
}
649649

650650
private <T> String formatTransientEntityExceptionMessage(T entity, RelationalPersistentEntity<T> persistentEntity) {
651651

652652
return String.format("Failed to update table [%s]; Row with Id [%s] does not exist",
653-
persistentEntity.getTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier());
653+
persistentEntity.getFullTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier());
654654
}
655655

656656
@SuppressWarnings("unchecked")
@@ -744,14 +744,14 @@ private <T> Query getByIdQuery(T entity, RelationalPersistentEntity<?> persisten
744744
}
745745

746746
SqlIdentifier getTableName(Class<?> entityClass) {
747-
return getRequiredEntity(entityClass).getTableName();
747+
return getRequiredEntity(entityClass).getFullTableName();
748748
}
749749

750750
SqlIdentifier getTableNameOrEmpty(Class<?> entityClass) {
751751

752752
RelationalPersistentEntity<?> entity = this.mappingContext.getPersistentEntity(entityClass);
753753

754-
return entity != null ? entity.getTableName() : SqlIdentifier.EMPTY;
754+
return entity != null ? entity.getFullTableName() : SqlIdentifier.EMPTY;
755755
}
756756

757757
private RelationalPersistentEntity<?> getRequiredEntity(Class<?> entityClass) {

spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,9 @@ public String getSchema() {
8282
public String getColumnName(RelationalPersistentProperty property) {
8383
return columnNames.computeIfAbsent(property, delegate::getColumnName);
8484
}
85+
86+
@Override
87+
public void setForeignKeyNaming(ForeignKeyNaming foreignKeyNaming) {
88+
delegate.setForeignKeyNaming(foreignKeyNaming);
89+
}
8590
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.relational.core.mapping;
17+
18+
import org.jetbrains.annotations.NotNull;
19+
import org.springframework.util.Assert;
20+
21+
/**
22+
* The default naming strategy used by Spring Data Relational. Names are in SNAKE_CASE.
23+
*
24+
* @author Jens Schauder
25+
* @since 2.4
26+
*/
27+
public class DefaultNamingStrategy implements NamingStrategy {
28+
29+
/**
30+
* Since in most cases it doesn't make sense to have more than one {@link NamingStrategy} use of this instance is
31+
* recommended.
32+
*/
33+
public static NamingStrategy INSTANCE = new DefaultNamingStrategy();
34+
35+
private ForeignKeyNaming foreignKeyNaming = ForeignKeyNaming.IGNORE_RENAMING;
36+
37+
@Override
38+
public void setForeignKeyNaming(ForeignKeyNaming foreignKeyNaming) {
39+
40+
Assert.notNull(foreignKeyNaming, "foreignKeyNaming must not be null");
41+
42+
this.foreignKeyNaming = foreignKeyNaming;
43+
}
44+
45+
@Override
46+
public String getReverseColumnName(RelationalPersistentProperty property) {
47+
48+
return getColumnNameReferencing(property.getOwner());
49+
}
50+
51+
@Override
52+
public String getReverseColumnName(PersistentPropertyPathExtension path) {
53+
54+
RelationalPersistentEntity<?> leafEntity = path.getIdDefiningParentPath().getLeafEntity();
55+
56+
return getColumnNameReferencing(leafEntity);
57+
}
58+
59+
private String getColumnNameReferencing(RelationalPersistentEntity<?> leafEntity) {
60+
61+
Assert.state(leafEntity != null, "Leaf Entity must not be null.");
62+
63+
if (foreignKeyNaming == ForeignKeyNaming.IGNORE_RENAMING) {
64+
return getTableName(leafEntity.getType());
65+
}
66+
67+
return leafEntity.getSimpleTableName().getReference();
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.relational.core.mapping;
17+
18+
/**
19+
* Enum for determining how the names of back references should get generated.
20+
*
21+
* @author Jens Schauder
22+
* @since 2.4
23+
*/
24+
public enum ForeignKeyNaming {
25+
/**
26+
* This strategy takes names specified via {@link Table} annotation into account.
27+
*/
28+
APPLY_RENAMING,
29+
/**
30+
* This strategy does not take names specified via {@link Table} annotation into account.
31+
*/
32+
IGNORE_RENAMING
33+
}

0 commit comments

Comments
 (0)