From ed2ea4aa1ecd72ae0a650d342835a0fee2e4e522 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 22 Jan 2026 08:35:56 +0100 Subject: [PATCH] Support TypedPropertyPath for queries and update. Closes #2226 Original pull request: #2227 --- .../data/r2dbc/query/CriteriaUnitTests.java | 40 +++++++++---------- .../data/relational/core/query/Criteria.java | 37 +++++++++++++++++ .../data/relational/core/query/Query.java | 21 +++++++++- .../data/relational/core/query/Update.java | 27 +++++++++++++ .../core/query/UpdateUnitTests.java | 19 +++++++++ 5 files changed, 123 insertions(+), 21 deletions(-) diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/query/CriteriaUnitTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/query/CriteriaUnitTests.java index 6850ad6f8..073b75ee7 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/query/CriteriaUnitTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/query/CriteriaUnitTests.java @@ -34,7 +34,7 @@ import org.springframework.data.relational.core.sql.SqlIdentifier; */ class CriteriaUnitTests { - @Test // gh-289 + @Test // GH-289 void fromCriteria() { Criteria nested1 = where("foo").isNotNull(); @@ -46,7 +46,7 @@ class CriteriaUnitTests { assertThat(criteria.getPrevious()).isEqualTo(Criteria.empty()); } - @Test // gh-289 + @Test // GH-289 void fromCriteriaOptimized() { Criteria nested = where("foo").is("bar").and("baz").isNotNull(); @@ -55,7 +55,7 @@ class CriteriaUnitTests { assertThat(criteria).isSameAs(nested); } - @Test // gh-289 + @Test // GH-289 void isEmpty() { SoftAssertions.assertSoftly(softly -> { @@ -77,7 +77,7 @@ class CriteriaUnitTests { }); } - @Test // gh-64 + @Test // GH-64 void andChainedCriteria() { Criteria criteria = where("foo").is("bar").and("baz").isNotNull(); @@ -95,7 +95,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo("bar"); } - @Test // gh-289 + @Test // GH-289 void andGroupedCriteria() { Criteria criteria = where("foo").is("bar").and(where("foo").is("baz")); @@ -113,7 +113,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo("bar"); } - @Test // gh-64 + @Test // GH-64 void orChainedCriteria() { Criteria criteria = where("foo").is("bar").or("baz").isNotNull(); @@ -128,7 +128,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo("bar"); } - @Test // gh-289 + @Test // GH-289 void orGroupedCriteria() { Criteria criteria = where("foo").is("bar").or(where("foo").is("baz")); @@ -146,7 +146,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo("bar"); } - @Test // gh-64 + @Test // GH-64 void shouldBuildEqualsCriteria() { Criteria criteria = where("foo").is("bar"); @@ -166,7 +166,7 @@ class CriteriaUnitTests { assertThat(criteria.isIgnoreCase()).isTrue(); } - @Test // gh-64 + @Test // GH-64 void shouldBuildNotEqualsCriteria() { Criteria criteria = where("foo").not("bar"); @@ -176,7 +176,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo("bar"); } - @Test // gh-64 + @Test // GH-64 void shouldBuildInCriteria() { Criteria criteria = where("foo").in("bar", "baz"); @@ -186,7 +186,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo(Arrays.asList("bar", "baz")); } - @Test // gh-64 + @Test // GH-64 void shouldBuildNotInCriteria() { Criteria criteria = where("foo").notIn("bar", "baz"); @@ -196,7 +196,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo(Arrays.asList("bar", "baz")); } - @Test // gh-64 + @Test // GH-64 void shouldBuildGtCriteria() { Criteria criteria = where("foo").greaterThan(1); @@ -206,7 +206,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo(1); } - @Test // gh-64 + @Test // GH-64 void shouldBuildGteCriteria() { Criteria criteria = where("foo").greaterThanOrEquals(1); @@ -216,7 +216,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo(1); } - @Test // gh-64 + @Test // GH-64 void shouldBuildLtCriteria() { Criteria criteria = where("foo").lessThan(1); @@ -226,7 +226,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo(1); } - @Test // gh-64 + @Test // GH-64 void shouldBuildLteCriteria() { Criteria criteria = where("foo").lessThanOrEquals(1); @@ -236,7 +236,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo(1); } - @Test // gh-64 + @Test // GH-64 void shouldBuildLikeCriteria() { Criteria criteria = where("foo").like("hello%"); @@ -255,7 +255,7 @@ class CriteriaUnitTests { assertThat(criteria.getValue()).isEqualTo("hello%"); } - @Test // gh-64 + @Test // GH-64 void shouldBuildIsNullCriteria() { Criteria criteria = where("foo").isNull(); @@ -264,7 +264,7 @@ class CriteriaUnitTests { assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_NULL); } - @Test // gh-64 + @Test // GH-64 void shouldBuildIsNotNullCriteria() { Criteria criteria = where("foo").isNotNull(); @@ -273,7 +273,7 @@ class CriteriaUnitTests { assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_NOT_NULL); } - @Test // gh-282 + @Test // GH-282 void shouldBuildIsTrueCriteria() { Criteria criteria = where("foo").isTrue(); @@ -282,7 +282,7 @@ class CriteriaUnitTests { assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_TRUE); } - @Test // gh-282 + @Test // GH-282 void shouldBuildIsFalseCriteria() { Criteria criteria = where("foo").isFalse(); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java index 6381963a5..63b16bfd5 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java @@ -26,6 +26,7 @@ import java.util.StringJoiner; import org.jspecify.annotations.Nullable; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.util.Pair; @@ -54,6 +55,7 @@ import org.springframework.util.Assert; * @author Oliver Drotbohm * @author Roman Chigvintsev * @author Jens Schauder + * @author Christoph Strobl * @since 2.0 */ public class Criteria implements CriteriaDefinition { @@ -157,6 +159,17 @@ public class Criteria implements CriteriaDefinition { return new DefaultCriteriaStep(SqlIdentifier.unquoted(column)); } + /** + * Static factory method to create a Criteria using the provided {@code path}. + * + * @param property Must not be {@literal null}. + * @return a new {@link CriteriaStep} object to complete the first {@link Criteria}. + * @since 4.1 + */ + public static CriteriaStep where(TypedPropertyPath property) { + return where(property.toDotPath()); + } + /** * Create a new {@link Criteria} and combine it with {@code AND} using the provided {@code column} name. * @@ -177,6 +190,18 @@ public class Criteria implements CriteriaDefinition { }; } + /** + * Create a new {@link Criteria} and combine it with {@code AND} using the provided {@code path}. + * + * @param property Must not be {@literal null}. + * @return a new {@link CriteriaStep} object to complete the next {@link Criteria}. + * @since 4.1 + */ + @CheckReturnValue + public CriteriaStep and(TypedPropertyPath property) { + return and(TypedPropertyPath.of(property).toDotPath()); + } + /** * Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link Criteria} group. * @@ -227,6 +252,18 @@ public class Criteria implements CriteriaDefinition { }; } + /** + * Create a new {@link Criteria} and combine it with {@code OR} using the provided {@code path}. + * + * @param property Must not be {@literal null}. + * @return a new {@link CriteriaStep} object to complete the next {@link Criteria}. + * @since 4.1 + */ + @CheckReturnValue + public CriteriaStep or(TypedPropertyPath property) { + return or(TypedPropertyPath.of(property).toDotPath()); + } + /** * Create a new {@link Criteria} and combine it as group with {@code OR} using the provided {@link Criteria} group. * diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java index 70741deda..130e9bd9f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java @@ -22,8 +22,10 @@ import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jspecify.annotations.Nullable; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.relational.core.sql.SqlIdentifier; @@ -35,6 +37,7 @@ import org.springframework.util.Assert; * class are designed to be used in a fluent style creating immutable objects. * * @author Mark Paluch + * @author Christoph Strobl * @since 2.0 * @see Criteria * @see Sort @@ -114,7 +117,19 @@ public class Query { Assert.notNull(columns, "Columns must not be null"); - return withColumns(columns.stream().map(SqlIdentifier::unquoted).collect(Collectors.toList())); + return withColumns(columns.stream()); + } + + /** + * Add columns to the query. + * + * @param properties + * @return a new {@link Query} object containing the former settings with {@code columns} applied. + * @since 4.1 + */ + @CheckReturnValue + public Query columnsOf(Collection> properties) { + return withColumns(properties.stream().map(TypedPropertyPath::of).map(TypedPropertyPath::toDotPath)); } /** @@ -132,6 +147,10 @@ public class Query { return withColumns(Arrays.asList(columns)); } + private Query withColumns(Stream columns) { + return withColumns(columns.map(SqlIdentifier::unquoted).collect(Collectors.toList())); + } + /** * Add columns to the query. * diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Update.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Update.java index f31aee699..b8dfd31a4 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Update.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Update.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.StringJoiner; import org.jspecify.annotations.Nullable; +import org.springframework.data.core.TypedPropertyPath; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.lang.CheckReturnValue; @@ -32,6 +33,7 @@ import org.springframework.util.Assert; * * @author Mark Paluch * @author Oliver Drotbohm + * @author Christoph Strobl * @since 2.0 */ public class Update { @@ -65,6 +67,18 @@ public class Update { return EMPTY.set(column, value); } + /** + * Static factory method to create an {@link Update} using the provided path. + * + * @param property must not be {@literal null}. + * @param value can be {@literal null}. + * @return new instance of {@link Update}. + * @since 4.1 + */ + public static Update update(TypedPropertyPath property, @Nullable Object value) { + return update(property.toDotPath(), value); + } + /** * Update a column by assigning a value. * @@ -80,6 +94,19 @@ public class Update { return addMultiFieldOperation(SqlIdentifier.unquoted(column), value); } + /** + * Update a path by assigning a value. + * + * @param property must not be {@literal null}. + * @param value can be {@literal null}. + * @return new instance of {@link Update}. + * @since 4.1 + */ + @CheckReturnValue + public Update set(TypedPropertyPath property, @Nullable Object value) { + return set(property.toDotPath(), value); + } + /** * Update a column by assigning a value. * diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/query/UpdateUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/query/UpdateUnitTests.java index a7ebe23c7..1f56a6c5f 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/query/UpdateUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/query/UpdateUnitTests.java @@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.*; * Unit tests for {@link Update}. * * @author Mark Paluch + * @author Christoph Strobl */ public class UpdateUnitTests { @@ -31,4 +32,22 @@ public class UpdateUnitTests { assertThat(Update.update("foo", "baz").set("bar", 42)).hasToString("SET foo = 'baz', bar = 42"); } + + @Test // GH-2226 + public void shouldRenderUpdateWithTypedPropertyPathToString() { + assertThat(Update.update(Person::getFirstName, "baz").set("bar", 42)).hasToString("SET firstName = 'baz', bar = 42"); + } + + static class Person { + private String firstName; + private String lastName; + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } }