Browse Source

Add expression support for `@MappedCollection` annotation.

See #1325
Original pull request: #1461
pull/1526/head
Mark Paluch 3 years ago
parent
commit
b92586f8c0
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 89
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java
  2. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/ExpressionEvaluator.java
  3. 8
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/MappedCollection.java
  4. 9
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java
  5. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/SqlIdentifierSanitizer.java
  6. 41
      spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java

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

@ -27,7 +27,6 @@ import org.springframework.data.relational.core.mapping.Embedded.OnEmpty; @@ -27,7 +27,6 @@ import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.Optionals;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.LiteralExpression;
@ -53,12 +52,14 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent @@ -53,12 +52,14 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
private final Lazy<SqlIdentifier> columnName;
private final @Nullable Expression columnNameExpression;
private final Lazy<Optional<SqlIdentifier>> collectionIdColumnName;
private final @Nullable Expression collectionIdColumnNameExpression;
private final Lazy<SqlIdentifier> collectionKeyColumnName;
private final @Nullable Expression collectionKeyColumnNameExpression;
private final boolean isEmbedded;
private final String embeddedPrefix;
private final NamingStrategy namingStrategy;
private boolean forceQuote = true;
private ExpressionEvaluator spelExpressionProcessor = new ExpressionEvaluator(EvaluationContextProvider.DEFAULT);
private ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(EvaluationContextProvider.DEFAULT);
/**
* Creates a new {@link BasicRelationalPersistentProperty}.
@ -99,38 +100,58 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent @@ -99,38 +100,58 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
.map(Embedded::prefix) //
.orElse("");
Lazy<Optional<SqlIdentifier>> collectionIdColumnName = null;
Lazy<SqlIdentifier> collectionKeyColumnName = Lazy
.of(() -> createDerivedSqlIdentifier(namingStrategy.getKeyColumn(this)));
if (isAnnotationPresent(MappedCollection.class)) {
MappedCollection mappedCollection = getRequiredAnnotation(MappedCollection.class);
if (StringUtils.hasText(mappedCollection.idColumn())) {
collectionIdColumnName = Lazy.of(() -> Optional.of(createSqlIdentifier(mappedCollection.idColumn())));
}
this.collectionIdColumnNameExpression = detectExpression(mappedCollection.idColumn());
collectionKeyColumnName = Lazy.of(
() -> StringUtils.hasText(mappedCollection.keyColumn()) ? createSqlIdentifier(mappedCollection.keyColumn())
: createDerivedSqlIdentifier(namingStrategy.getKeyColumn(this)));
this.collectionKeyColumnNameExpression = detectExpression(mappedCollection.keyColumn());
} else {
this.collectionIdColumnNameExpression = null;
this.collectionKeyColumnNameExpression = null;
}
if (isAnnotationPresent(Column.class)) {
Column column = getRequiredAnnotation(Column.class);
columnName = Lazy.of(() -> StringUtils.hasText(column.value()) ? createSqlIdentifier(column.value())
this.columnName = Lazy.of(() -> StringUtils.hasText(column.value()) ? createSqlIdentifier(column.value())
: createDerivedSqlIdentifier(namingStrategy.getColumnName(this)));
columnNameExpression = detectExpression(column.value());
this.columnNameExpression = detectExpression(column.value());
if (collectionIdColumnName == null && StringUtils.hasText(column.value())) {
collectionIdColumnName = Lazy.of(() -> Optional.of(createSqlIdentifier(column.value())));
}
} else {
columnName = Lazy.of(() -> createDerivedSqlIdentifier(namingStrategy.getColumnName(this)));
columnNameExpression = null;
this.columnName = Lazy.of(() -> createDerivedSqlIdentifier(namingStrategy.getColumnName(this)));
this.columnNameExpression = null;
}
// TODO: support expressions for MappedCollection
this.collectionIdColumnName = Lazy.of(() -> Optionals
.toStream(Optional.ofNullable(findAnnotation(MappedCollection.class)) //
.map(MappedCollection::idColumn), //
Optional.ofNullable(findAnnotation(Column.class)) //
.map(Column::value)) //
.filter(StringUtils::hasText) //
.findFirst() //
.map(this::createSqlIdentifier)); //
this.collectionKeyColumnName = Lazy.of(() -> Optionals //
.toStream(Optional.ofNullable(findAnnotation(MappedCollection.class)).map(MappedCollection::keyColumn)) //
.filter(StringUtils::hasText).findFirst() //
.map(this::createSqlIdentifier) //
.orElseGet(() -> createDerivedSqlIdentifier(namingStrategy.getKeyColumn(this))));
if (collectionIdColumnName == null) {
collectionIdColumnName = Lazy.of(Optional.empty());
}
this.collectionIdColumnName = collectionIdColumnName;
this.collectionKeyColumnName = collectionKeyColumnName;
}
void setSpelExpressionProcessor(ExpressionEvaluator spelExpressionProcessor) {
this.spelExpressionProcessor = spelExpressionProcessor;
void setExpressionEvaluator(ExpressionEvaluator expressionEvaluator) {
this.expressionEvaluator = expressionEvaluator;
}
/**
@ -184,7 +205,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent @@ -184,7 +205,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
return columnName.get();
}
return createSqlIdentifier(spelExpressionProcessor.evaluate(columnNameExpression));
return createSqlIdentifier(expressionEvaluator.evaluate(columnNameExpression));
}
@Override
@ -195,13 +216,27 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent @@ -195,13 +216,27 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
@Override
public SqlIdentifier getReverseColumnName(PersistentPropertyPathExtension path) {
return collectionIdColumnName.get()
.orElseGet(() -> createDerivedSqlIdentifier(this.namingStrategy.getReverseColumnName(path)));
if (collectionIdColumnNameExpression == null) {
return collectionIdColumnName.get()
.orElseGet(() -> createDerivedSqlIdentifier(this.namingStrategy.getReverseColumnName(path)));
}
return createSqlIdentifier(expressionEvaluator.evaluate(collectionIdColumnNameExpression));
}
@Override
public SqlIdentifier getKeyColumn() {
return isQualified() ? collectionKeyColumnName.get() : null;
if (!isQualified()) {
return null;
}
if (collectionKeyColumnNameExpression == null) {
return collectionKeyColumnName.get();
}
return createSqlIdentifier(expressionEvaluator.evaluate(collectionKeyColumnNameExpression));
}
@Override

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

@ -14,7 +14,7 @@ import org.springframework.util.Assert; @@ -14,7 +14,7 @@ import org.springframework.util.Assert;
*
* @author Kurt Niemi
* @see SqlIdentifierSanitizer
* @since 3.1
* @since 3.2
*/
class ExpressionEvaluator {

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

@ -37,8 +37,9 @@ import java.util.Set; @@ -37,8 +37,9 @@ import java.util.Set;
public @interface MappedCollection {
/**
* The column name for id column in the corresponding relationship table. Defaults to {@link NamingStrategy} usage if
* the value is empty.
* The column name for id column in the corresponding relationship table. The attribute supports SpEL expressions to
* dynamically calculate the column name on a per-operation basis. Defaults to {@link NamingStrategy} usage if the
* value is empty.
*
* @see NamingStrategy#getReverseColumnName(RelationalPersistentProperty)
*/
@ -46,7 +47,8 @@ public @interface MappedCollection { @@ -46,7 +47,8 @@ public @interface MappedCollection {
/**
* The column name for key columns of {@link List} or {@link Map} collections in the corresponding relationship table.
* Defaults to {@link NamingStrategy} usage if the value is empty.
* The attribute supports SpEL expressions to dynamically calculate the column name on a per-operation basis. Defaults
* to {@link NamingStrategy} usage if the value is empty.
*
* @see NamingStrategy#getKeyColumn(RelationalPersistentProperty)
*/

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

@ -83,6 +83,13 @@ public class RelationalMappingContext @@ -83,6 +83,13 @@ public class RelationalMappingContext
this.forceQuote = forceQuote;
}
/**
* Set the {@link SqlIdentifierSanitizer} to sanitize
* {@link org.springframework.data.relational.core.sql.SqlIdentifier identifiers} created from SpEL expressions.
*
* @param sanitizer must not be {@literal null}.
* @since 3.2
*/
public void setSqlIdentifierSanitizer(SqlIdentifierSanitizer sanitizer) {
this.expressionEvaluator.setSanitizer(sanitizer);
}
@ -119,7 +126,7 @@ public class RelationalMappingContext @@ -119,7 +126,7 @@ public class RelationalMappingContext
protected void applyDefaults(BasicRelationalPersistentProperty persistentProperty) {
persistentProperty.setForceQuote(isForceQuote());
persistentProperty.setSpelExpressionProcessor(this.expressionEvaluator);
persistentProperty.setExpressionEvaluator(this.expressionEvaluator);
}
}

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

@ -9,7 +9,7 @@ import org.springframework.util.Assert; @@ -9,7 +9,7 @@ import org.springframework.util.Assert;
*
* @author Kurt Niemi
* @author Mark Paluch
* @since 3.1
* @since 3.2
* @see RelationalMappingContext#setSqlIdentifierSanitizer(SqlIdentifierSanitizer)
*/
@FunctionalInterface

41
spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentPropertyUnitTests.java

@ -72,14 +72,24 @@ public class BasicRelationalPersistentPropertyUnitTests { @@ -72,14 +72,24 @@ public class BasicRelationalPersistentPropertyUnitTests {
@Test // GH-1325
void testRelationalPersistentEntitySpelExpressions() {
assertThat(entity.getRequiredPersistentProperty("spelExpression1").getColumnName()).isEqualTo(quoted("THE_FORCE_IS_WITH_YOU"));
assertThat(entity.getRequiredPersistentProperty("spelExpression1").getColumnName())
.isEqualTo(quoted("THE_FORCE_IS_WITH_YOU"));
assertThat(entity.getRequiredPersistentProperty("littleBobbyTables").getColumnName())
.isEqualTo(quoted("DROPALLTABLES"));
// Test that sanitizer does affect non-spel expressions
assertThat(entity.getRequiredPersistentProperty(
"poorDeveloperProgrammaticallyAskingToShootThemselvesInTheFoot").getColumnName())
.isEqualTo(quoted("--; DROP ALL TABLES;--"));
assertThat(entity.getRequiredPersistentProperty("poorDeveloperProgrammaticallyAskingToShootThemselvesInTheFoot")
.getColumnName()).isEqualTo(quoted("--; DROP ALL TABLES;--"));
}
@Test // GH-1325
void shouldEvaluateMappedCollectionExpressions() {
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(WithMappedCollection.class);
RelationalPersistentProperty property = entity.getRequiredPersistentProperty("someList");
assertThat(property.getKeyColumn()).isEqualTo(quoted("key_col"));
assertThat(property.getReverseColumnName(null)).isEqualTo(quoted("id_col"));
}
@Test // DATAJDBC-111
@ -166,18 +176,16 @@ public class BasicRelationalPersistentPropertyUnitTests { @@ -166,18 +176,16 @@ public class BasicRelationalPersistentPropertyUnitTests {
public static String spelExpression1Value = "THE_FORCE_IS_WITH_YOU";
public static String littleBobbyTablesValue = "--; DROP ALL TABLES;--";
@Column(value="#{T(org.springframework.data.relational.core.mapping." +
"BasicRelationalPersistentPropertyUnitTests$DummyEntity" +
").spelExpression1Value}")
private String spelExpression1;
@Column(value = "#{T(org.springframework.data.relational.core.mapping."
+ "BasicRelationalPersistentPropertyUnitTests$DummyEntity"
+ ").spelExpression1Value}") private String spelExpression1;
@Column(value="#{T(org.springframework.data.relational.core.mapping." +
"BasicRelationalPersistentPropertyUnitTests$DummyEntity" +
").littleBobbyTablesValue}")
private String littleBobbyTables;
@Column(value = "#{T(org.springframework.data.relational.core.mapping."
+ "BasicRelationalPersistentPropertyUnitTests$DummyEntity"
+ ").littleBobbyTablesValue}") private String littleBobbyTables;
@Column(value="--; DROP ALL TABLES;--")
private String poorDeveloperProgrammaticallyAskingToShootThemselvesInTheFoot;
@Column(
value = "--; DROP ALL TABLES;--") private String poorDeveloperProgrammaticallyAskingToShootThemselvesInTheFoot;
// DATAJDBC-111
private @Embedded(onEmpty = OnEmpty.USE_NULL) EmbeddableEntity embeddableEntity;
@ -199,6 +207,11 @@ public class BasicRelationalPersistentPropertyUnitTests { @@ -199,6 +207,11 @@ public class BasicRelationalPersistentPropertyUnitTests {
}
}
static class WithMappedCollection {
@MappedCollection(idColumn = "#{'id_col'}", keyColumn = "#{'key_col'}") private List<Integer> someList;
}
@SuppressWarnings("unused")
private enum SomeEnum {
ALPHA

Loading…
Cancel
Save