diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMapper.java index 65d9d3c231..b4dcd05b64 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMapper.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.mapping.JdbcValue; @@ -60,7 +61,6 @@ public class QueryMapper { private final JdbcConverter converter; - private final Dialect dialect; private final MappingContext, RelationalPersistentProperty> mappingContext; /** @@ -68,18 +68,31 @@ public class QueryMapper { * * @param dialect must not be {@literal null}. * @param converter must not be {@literal null}. + * @deprecated use {@link QueryMapper(JdbcConverter)} instead. */ - @SuppressWarnings({ "unchecked", "rawtypes" }) + @Deprecated(since="3.2") public QueryMapper(Dialect dialect, JdbcConverter converter) { Assert.notNull(dialect, "Dialect must not be null"); Assert.notNull(converter, "JdbcConverter must not be null"); this.converter = converter; - this.dialect = dialect; this.mappingContext = (MappingContext) converter.getMappingContext(); } + /** + * Creates a new {@link QueryMapper} with the given {@link JdbcConverter}. + * + * @param converter must not be {@literal null}. + */ + public QueryMapper( JdbcConverter converter) { + + Assert.notNull(converter, "JdbcConverter must not be null"); + + this.converter = converter; + this.mappingContext = converter.getMappingContext(); + } + /** * Map the {@link Sort} object to apply field name mapping using {@link RelationalPersistentEntity the type to read}. * @@ -295,16 +308,13 @@ private Condition mapCondition(CriteriaDefinition criteria, MapSqlParameterSourc mappedValue = convertValue(comparator, settableValue.getValue(), propertyField.getTypeHint()); sqlType = getTypeHint(mappedValue, actualType.getType(), settableValue); - } else if (criteria.getValue() instanceof ValueFunction) { + } else if (criteria.getValue() instanceof ValueFunction valueFunction) { - ValueFunction valueFunction = (ValueFunction) criteria.getValue(); - Object value = valueFunction.apply(getEscaper(comparator)); - - mappedValue = convertValue(comparator, value, propertyField.getTypeHint()); + mappedValue = valueFunction; sqlType = propertyField.getSqlType(); - } else if (propertyField instanceof MetadataBackedField // - && ((MetadataBackedField) propertyField).property != null // + } else if (propertyField instanceof MetadataBackedField metadataBackedField // + && metadataBackedField.property != null // && (criteria.getValue() == null || !criteria.getValue().getClass().isArray())) { RelationalPersistentProperty property = ((MetadataBackedField) propertyField).property; @@ -431,15 +441,6 @@ private Condition mapEmbeddedObjectCondition(CriteriaDefinition criteria, MapSql return Conditions.nest(condition); } - private Escaper getEscaper(Comparator comparator) { - - if (comparator == Comparator.LIKE || comparator == Comparator.NOT_LIKE) { - return dialect.getLikeEscaper(); - } - - return Escaper.DEFAULT; - } - @Nullable private Object convertValue(Comparator comparator, @Nullable Object value, TypeInformation typeHint) { 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 60e69edec7..3058106226 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 @@ -113,7 +113,7 @@ class SqlGenerator { this.renderContext = new RenderContextFactory(dialect).createRenderContext(); this.sqlRenderer = SqlRenderer.create(renderContext); this.columns = new Columns(entity, mappingContext, converter); - this.queryMapper = new QueryMapper(dialect, converter); + this.queryMapper = new QueryMapper(converter); this.dialect = dialect; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/EscapingParameterSource.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/EscapingParameterSource.java new file mode 100644 index 0000000000..abc85b3d11 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/EscapingParameterSource.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023 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.jdbc.repository.query; + +import org.springframework.data.relational.core.dialect.Escaper; +import org.springframework.data.relational.core.query.ValueFunction; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; + +/** + * This {@link SqlParameterSource} will apply escaping to it's values. + * + * @author Jens Schauder + * @since 3.2 + */ +public class EscapingParameterSource implements SqlParameterSource { + private final SqlParameterSource parameterSource; + private final Escaper escaper; + + public EscapingParameterSource(SqlParameterSource parameterSource, Escaper escaper) { + + this.parameterSource = parameterSource; + this.escaper = escaper; + } + + @Override + public boolean hasValue(String paramName) { + return parameterSource.hasValue(paramName); + } + + @Override + public Object getValue(String paramName) throws IllegalArgumentException { + + Object value = parameterSource.getValue(paramName); + if (value instanceof ValueFunction) { + return ((ValueFunction) value).apply(escaper); + } + return value; + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java index b1cc21571b..7411036ab5 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java @@ -102,7 +102,7 @@ class JdbcQueryCreator extends RelationalQueryCreator { this.accessor = accessor; this.entityMetadata = entityMetadata; - this.queryMapper = new QueryMapper(dialect, converter); + this.queryMapper = new QueryMapper(converter); this.renderContextFactory = new RenderContextFactory(dialect); this.isSliceQuery = isSliceQuery; this.returnedType = returnedType; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ParametrizedQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ParametrizedQuery.java index b41e2f87f5..ac3c256d27 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ParametrizedQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ParametrizedQuery.java @@ -15,12 +15,15 @@ */ package org.springframework.data.jdbc.repository.query; +import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.dialect.Escaper; import org.springframework.jdbc.core.namedparam.SqlParameterSource; /** * Value object encapsulating a query containing named parameters and a{@link SqlParameterSource} to bind the parameters. * * @author Mark Paluch + * @author Jens Schauder * @since 2.0 */ class ParametrizedQuery { @@ -38,12 +41,13 @@ String getQuery() { return query; } - SqlParameterSource getParameterSource() { - return parameterSource; - } - @Override public String toString() { return this.query; } + + public SqlParameterSource getParameterSource(Escaper escaper) { + + return new EscapingParameterSource(parameterSource, escaper); + } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java index 01876f0d66..ecdbbc5152 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java @@ -126,7 +126,7 @@ public Object execute(Object[] values) { ParametrizedQuery query = createQuery(accessor, processor.getReturnedType()); JdbcQueryExecution execution = getQueryExecution(processor, accessor); - return execution.execute(query.getQuery(), query.getParameterSource()); + return execution.execute(query.getQuery(), query.getParameterSource(dialect.getLikeEscaper())); } private JdbcQueryExecution getQueryExecution(ResultProcessor processor, @@ -164,7 +164,7 @@ private JdbcQueryExecution getQueryExecution(ResultProcessor processor, ParametrizedQuery countQuery = queryCreator.createQuery(Sort.unsorted()); Object count = singleObjectQuery((rs, i) -> rs.getLong(1)).execute(countQuery.getQuery(), - countQuery.getParameterSource()); + countQuery.getParameterSource(dialect.getLikeEscaper())); return converter.getConversionService().convert(count, Long.class); }); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java index 185c6d24c5..d2526fc9f2 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java @@ -50,7 +50,7 @@ public class QueryMapperUnitTests { JdbcMappingContext context = new JdbcMappingContext(); JdbcConverter converter = new BasicJdbcConverter(context, mock(RelationResolver.class)); - QueryMapper mapper = new QueryMapper(PostgresDialect.INSTANCE, converter); + QueryMapper mapper = new QueryMapper(converter); MapSqlParameterSource parameterSource = new MapSqlParameterSource(); @Test // DATAJDBC-318 diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java index 74b92b47a8..8c13c68de6 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java @@ -36,6 +36,7 @@ import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.relational.core.dialect.Escaper; import org.springframework.data.relational.core.dialect.H2Dialect; import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.MappedCollection; @@ -93,7 +94,7 @@ public void createQueryByAggregateReference() throws Exception { softly.assertThat(query.getQuery()) .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"HOBBY_REFERENCE\" = :hobby_reference"); - softly.assertThat(query.getParameterSource().getValue("hobby_reference")).isEqualTo("twentythree"); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("hobby_reference")).isEqualTo("twentythree"); }); } @@ -112,8 +113,8 @@ void createQueryWithPessimisticWriteLock() throws Exception { softly.assertThat(query.getQuery().toUpperCase()).endsWith("FOR UPDATE"); - softly.assertThat(query.getParameterSource().getValue("first_name")).isEqualTo(firstname); - softly.assertThat(query.getParameterSource().getValue("last_name")).isEqualTo(lastname); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("first_name")).isEqualTo(firstname); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("last_name")).isEqualTo(lastname); }); } @@ -133,8 +134,8 @@ void createQueryWithPessimisticReadLock() throws Exception { // this is also for update since h2 dialect does not distinguish between lockmodes softly.assertThat(query.getQuery().toUpperCase()).endsWith("FOR UPDATE"); - softly.assertThat(query.getParameterSource().getValue("first_name")).isEqualTo(firstname); - softly.assertThat(query.getParameterSource().getValue("age")).isEqualTo(age); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("first_name")).isEqualTo(firstname); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("age")).isEqualTo(age); }); } @@ -165,7 +166,7 @@ public void createQueryForQueryByAggregateReference() throws Exception { softly.assertThat(query.getQuery()) .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"HOBBY_REFERENCE\" = :hobby_reference"); - softly.assertThat(query.getParameterSource().getValue("hobby_reference")).isEqualTo("twentythree"); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("hobby_reference")).isEqualTo("twentythree"); }); } @@ -182,7 +183,7 @@ public void createQueryForQueryByAggregateReferenceId() throws Exception { softly.assertThat(query.getQuery()) .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"HOBBY_REFERENCE\" = :hobby_reference"); - softly.assertThat(query.getParameterSource().getValue("hobby_reference")).isEqualTo("twentythree"); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("hobby_reference")).isEqualTo("twentythree"); }); } @@ -270,8 +271,8 @@ public void createsQueryToFindAllEntitiesByDateAttributeBetween() throws Excepti softly.assertThat(query.getQuery()) .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" BETWEEN :date_of_birth AND :date_of_birth1"); - softly.assertThat(query.getParameterSource().getValue("date_of_birth")).isEqualTo(from); - softly.assertThat(query.getParameterSource().getValue("date_of_birth1")).isEqualTo(to); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("date_of_birth")).isEqualTo(from); + softly.assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("date_of_birth1")).isEqualTo(to); }); } @@ -405,7 +406,7 @@ public void appendsLikeOperatorParameterWithPercentSymbolForStartingWithQuery() ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType); assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); - assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("Jo%"); + assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("first_name")).isEqualTo("Jo%"); } @Test // DATAJDBC-318 @@ -428,7 +429,7 @@ public void prependsLikeOperatorParameterWithPercentSymbolForEndingWithQuery() t ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType); assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); - assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%hn"); + assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("first_name")).isEqualTo("%hn"); } @Test // DATAJDBC-318 @@ -451,7 +452,7 @@ public void wrapsLikeOperatorParameterWithPercentSymbolsForContainingQuery() thr ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType); assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); - assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%oh%"); + assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("first_name")).isEqualTo("%oh%"); } @Test // DATAJDBC-318 @@ -474,7 +475,7 @@ public void wrapsLikeOperatorParameterWithPercentSymbolsForNotContainingQuery() ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType); assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name"); - assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%oh%"); + assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("first_name")).isEqualTo("%oh%"); } @Test // DATAJDBC-318 @@ -638,8 +639,8 @@ public void createsQueryByEmbeddedObject() throws Exception { .contains(TABLE + ".\"USER_STREET\" = :user_street", // " AND ", // TABLE + ".\"USER_CITY\" = :user_city"); - assertThat(query.getParameterSource().getValue("user_street")).isEqualTo("Hello"); - assertThat(query.getParameterSource().getValue("user_city")).isEqualTo("World"); + assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("user_street")).isEqualTo("Hello"); + assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("user_city")).isEqualTo("World"); } @Test // DATAJDBC-318 @@ -653,7 +654,7 @@ public void createsQueryByEmbeddedProperty() throws Exception { String expectedSql = BASE_SELECT + " WHERE " + TABLE + ".\"USER_STREET\" = :user_street"; assertThat(query.getQuery()).isEqualTo(expectedSql); - assertThat(query.getParameterSource().getValue("user_street")).isEqualTo("Hello"); + assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("user_street")).isEqualTo("Hello"); } @Test // DATAJDBC-534