From f2224a921e58532c181388c446aaf98da518875a Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 6 Jan 2025 12:50:57 +0100 Subject: [PATCH] Ignore collection like attributes for query by example. Collection valued attributes now get ignored. Before RelationalExampleMapper tried to generate predicates for these, resulting in invalid SQL. Closes #1969 --- .../query/RelationalExampleMapper.java | 5 + .../query/RelationalExampleMapperTests.java | 149 ++++++------------ .../modules/ROOT/pages/query-by-example.adoc | 1 + 3 files changed, 56 insertions(+), 99 deletions(-) diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalExampleMapper.java b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalExampleMapper.java index ce207e9e862..5bfd13f5832 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalExampleMapper.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalExampleMapper.java @@ -38,6 +38,7 @@ * * @since 2.2 * @author Greg Turnquist + * @author Jens Schauder */ public class RelationalExampleMapper { @@ -78,6 +79,10 @@ private Query getMappedExample(Example example, RelationalPersistentEntit entity.doWithProperties((PropertyHandler) property -> { + if (property.isCollectionLike() || property.isMap()) { + return; + } + if (matcherAccessor.isIgnoredPath(property.getName())) { return; } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/repository/query/RelationalExampleMapperTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/repository/query/RelationalExampleMapperTests.java index be447e4e44d..963951c1197 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/repository/query/RelationalExampleMapperTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/repository/query/RelationalExampleMapperTests.java @@ -16,6 +16,15 @@ package org.springframework.data.relational.repository.query; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.domain.ExampleMatcher.*; +import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.*; +import static org.springframework.data.domain.ExampleMatcher.StringMatcher.*; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; @@ -23,13 +32,7 @@ import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.query.Query; - -import java.util.Objects; - -import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.*; -import static org.springframework.data.domain.ExampleMatcher.StringMatcher.*; -import static org.springframework.data.domain.ExampleMatcher.*; +import org.springframework.lang.Nullable; /** * Verify that the {@link RelationalExampleMapper} properly turns {@link Example}s into {@link Query}'s. @@ -48,8 +51,7 @@ public void before() { @Test // GH-929 void queryByExampleWithId() { - Person person = new Person(); - person.setId("id1"); + Person person = new Person("id1", null, null, null, null, null); Example example = Example.of(person); @@ -63,8 +65,7 @@ void queryByExampleWithId() { @Test // GH-929 void queryByExampleWithFirstname() { - Person person = new Person(); - person.setFirstname("Frodo"); + Person person = new Person(null, "Frodo", null, null, null, null); Example example = Example.of(person); @@ -78,9 +79,7 @@ void queryByExampleWithFirstname() { @Test // GH-929 void queryByExampleWithFirstnameAndLastname() { - Person person = new Person(); - person.setFirstname("Frodo"); - person.setLastname("Baggins"); + Person person = new Person(null, "Frodo", "Baggins", null, null, null); Example example = Example.of(person); @@ -94,8 +93,7 @@ void queryByExampleWithFirstnameAndLastname() { @Test // GH-929 void queryByExampleWithNullMatchingLastName() { - Person person = new Person(); - person.setLastname("Baggins"); + Person person = new Person(null, null, "Baggins", null, null, null); ExampleMatcher matcher = matching().withIncludeNullValues(); Example example = Example.of(person, matcher); @@ -110,9 +108,7 @@ void queryByExampleWithNullMatchingLastName() { @Test // GH-929 void queryByExampleWithNullMatchingFirstnameAndLastname() { - Person person = new Person(); - person.setFirstname("Bilbo"); - person.setLastname("Baggins"); + Person person = new Person(null, "Bilbo", "Baggins", null, null, null); ExampleMatcher matcher = matching().withIncludeNullValues(); Example example = Example.of(person, matcher); @@ -127,9 +123,7 @@ void queryByExampleWithNullMatchingFirstnameAndLastname() { @Test // GH-929 void queryByExampleWithFirstnameAndLastnameIgnoringFirstname() { - Person person = new Person(); - person.setFirstname("Frodo"); - person.setLastname("Baggins"); + Person person = new Person(null, "Bilbo", "Baggins", null, null, null); ExampleMatcher matcher = matching().withIgnorePaths("firstname"); Example example = Example.of(person, matcher); @@ -144,9 +138,7 @@ void queryByExampleWithFirstnameAndLastnameIgnoringFirstname() { @Test // GH-929 void queryByExampleWithFirstnameAndLastnameWithNullMatchingIgnoringFirstName() { - Person person = new Person(); - person.setFirstname("Frodo"); - person.setLastname("Baggins"); + Person person = new Person(null, "Bilbo", "Baggins", null, null, null); ExampleMatcher matcher = matching().withIncludeNullValues().withIgnorePaths("firstname"); Example example = Example.of(person, matcher); @@ -161,8 +153,7 @@ void queryByExampleWithFirstnameAndLastnameWithNullMatchingIgnoringFirstName() { @Test // GH-929 void queryByExampleWithFirstnameWithStringMatchingAtTheBeginning() { - Person person = new Person(); - person.setFirstname("Fro"); + Person person = new Person(null, "Fro", null, null, null, null); ExampleMatcher matcher = matching().withStringMatcher(STARTING); Example example = Example.of(person, matcher); @@ -177,8 +168,7 @@ void queryByExampleWithFirstnameWithStringMatchingAtTheBeginning() { @Test // GH-929 void queryByExampleWithFirstnameWithStringMatchingOnTheEnding() { - Person person = new Person(); - person.setFirstname("do"); + Person person = new Person(null, "do", null, null, null, null); ExampleMatcher matcher = matching().withStringMatcher(ENDING); Example example = Example.of(person, matcher); @@ -193,8 +183,7 @@ void queryByExampleWithFirstnameWithStringMatchingOnTheEnding() { @Test // GH-929 void queryByExampleWithFirstnameWithStringMatchingContaining() { - Person person = new Person(); - person.setFirstname("do"); + Person person = new Person(null, "do", null, null, null, null); ExampleMatcher matcher = matching().withStringMatcher(CONTAINING); Example example = Example.of(person, matcher); @@ -209,8 +198,7 @@ void queryByExampleWithFirstnameWithStringMatchingContaining() { @Test // GH-929 void queryByExampleWithFirstnameWithStringMatchingRegEx() { - Person person = new Person(); - person.setFirstname("do"); + Person person = new Person(null, "do", null, null, null, null); ExampleMatcher matcher = matching().withStringMatcher(ExampleMatcher.StringMatcher.REGEX); Example example = Example.of(person, matcher); @@ -222,8 +210,7 @@ void queryByExampleWithFirstnameWithStringMatchingRegEx() { @Test // GH-929 void queryByExampleWithFirstnameWithFieldSpecificStringMatcherEndsWith() { - Person person = new Person(); - person.setFirstname("do"); + Person person = new Person(null, "do", null, null, null, null); ExampleMatcher matcher = matching().withMatcher("firstname", endsWith()); Example example = Example.of(person, matcher); @@ -238,8 +225,7 @@ void queryByExampleWithFirstnameWithFieldSpecificStringMatcherEndsWith() { @Test // GH-929 void queryByExampleWithFirstnameWithFieldSpecificStringMatcherStartsWith() { - Person person = new Person(); - person.setFirstname("Fro"); + Person person = new Person(null, "Fro", null, null, null, null); ExampleMatcher matcher = matching().withMatcher("firstname", startsWith()); Example example = Example.of(person, matcher); @@ -254,8 +240,7 @@ void queryByExampleWithFirstnameWithFieldSpecificStringMatcherStartsWith() { @Test // GH-929 void queryByExampleWithFirstnameWithFieldSpecificStringMatcherContains() { - Person person = new Person(); - person.setFirstname("do"); + Person person = new Person(null, "do", null, null, null, null); ExampleMatcher matcher = matching().withMatcher("firstname", contains()); Example example = Example.of(person, matcher); @@ -270,8 +255,7 @@ void queryByExampleWithFirstnameWithFieldSpecificStringMatcherContains() { @Test // GH-929 void queryByExampleWithFirstnameWithStringMatchingAtTheBeginningIncludingNull() { - Person person = new Person(); - person.setFirstname("Fro"); + Person person = new Person(null, "Fro", null, null, null, null); ExampleMatcher matcher = matching().withStringMatcher(STARTING).withIncludeNullValues(); Example example = Example.of(person, matcher); @@ -286,8 +270,7 @@ void queryByExampleWithFirstnameWithStringMatchingAtTheBeginningIncludingNull() @Test // GH-929 void queryByExampleWithFirstnameWithStringMatchingOnTheEndingIncludingNull() { - Person person = new Person(); - person.setFirstname("do"); + Person person = new Person(null, "do", null, null, null, null); ExampleMatcher matcher = matching().withStringMatcher(ENDING).withIncludeNullValues(); Example example = Example.of(person, matcher); @@ -302,8 +285,7 @@ void queryByExampleWithFirstnameWithStringMatchingOnTheEndingIncludingNull() { @Test // GH-929 void queryByExampleWithFirstnameIgnoreCaseFieldLevel() { - Person person = new Person(); - person.setFirstname("fro"); + Person person = new Person(null, "fro", null, null, null, null); ExampleMatcher matcher = matching().withMatcher("firstname", startsWith().ignoreCase()); Example example = Example.of(person, matcher); @@ -320,8 +302,7 @@ void queryByExampleWithFirstnameIgnoreCaseFieldLevel() { @Test // GH-929 void queryByExampleWithFirstnameWithStringMatchingContainingIncludingNull() { - Person person = new Person(); - person.setFirstname("do"); + Person person = new Person(null, "do", null, null, null, null); ExampleMatcher matcher = matching().withStringMatcher(CONTAINING).withIncludeNullValues(); Example example = Example.of(person, matcher); @@ -336,8 +317,7 @@ void queryByExampleWithFirstnameWithStringMatchingContainingIncludingNull() { @Test // GH-929 void queryByExampleWithFirstnameIgnoreCase() { - Person person = new Person(); - person.setFirstname("Frodo"); + Person person = new Person(null, "Frodo", null, null, null, null); ExampleMatcher matcher = matching().withIgnoreCase(true); Example example = Example.of(person, matcher); @@ -354,9 +334,7 @@ void queryByExampleWithFirstnameIgnoreCase() { @Test // GH-929 void queryByExampleWithFirstnameOrLastname() { - Person person = new Person(); - person.setFirstname("Frodo"); - person.setLastname("Baggins"); + Person person = new Person(null, "Frodo", "Baggins", null, null, null); ExampleMatcher matcher = matchingAny(); Example example = Example.of(person, matcher); @@ -371,9 +349,7 @@ void queryByExampleWithFirstnameOrLastname() { @Test // GH-929 void queryByExampleEvenHandlesInvisibleFields() { - Person person = new Person(); - person.setFirstname("Frodo"); - person.setSecret("I have the ring!"); + Person person = new Person(null, "Frodo", null, "I have the ring!", null, null); Example example = Example.of(person); @@ -388,10 +364,7 @@ void queryByExampleEvenHandlesInvisibleFields() { @Test // GH-929 void queryByExampleSupportsPropertyTransforms() { - Person person = new Person(); - person.setFirstname("Frodo"); - person.setLastname("Baggins"); - person.setSecret("I have the ring!"); + Person person = new Person(null, "Frodo", "Baggins", "I have the ring!", null, null); ExampleMatcher matcher = matching() // .withTransformer("firstname", o -> { @@ -418,55 +391,33 @@ void queryByExampleSupportsPropertyTransforms() { "(secret = 'I have the ring!')"); } - static class Person { + @Test // GH-1969 + void collectionLikeAttributesGetIgnored() { - @Id - String id; - String firstname; - String lastname; - String secret; + Example example = Example.of(new Person(null, "Frodo", null, null, List.of(new Possession("Ring")), null)); - public Person(String id, String firstname, String lastname, String secret) { - this.id = id; - this.firstname = firstname; - this.lastname = lastname; - this.secret = secret; - } - - public Person() { - } + Query query = exampleMapper.getMappedExample(example); - // Override default visibility of getting the secret. - private String getSecret() { - return this.secret; - } + assertThat(query.getCriteria().orElseThrow().toString()).doesNotContainIgnoringCase("possession"); + } - public String getId() { - return this.id; - } + @Test // GH-1969 + void mapAttributesGetIgnored() { - public String getFirstname() { - return this.firstname; - } + Example example = Example.of(new Person(null, "Frodo", null, null, null, Map.of("Home", new Address("Bag End")))); - public String getLastname() { - return this.lastname; - } + Query query = exampleMapper.getMappedExample(example); - public void setId(String id) { - this.id = id; - } + assertThat(query.getCriteria().orElseThrow().toString()).doesNotContainIgnoringCase("address"); + } - public void setFirstname(String firstname) { - this.firstname = firstname; - } + record Person(@Id @Nullable String id, @Nullable String firstname, @Nullable String lastname, @Nullable String secret, + @Nullable List possessions,@Nullable Map addresses) { + } - public void setLastname(String lastname) { - this.lastname = lastname; - } + record Possession(String name) { + } - public void setSecret(String secret) { - this.secret = secret; - } + record Address(String description) { } } diff --git a/src/main/antora/modules/ROOT/pages/query-by-example.adoc b/src/main/antora/modules/ROOT/pages/query-by-example.adoc index b8c3c9a1407..491c57ca0b2 100644 --- a/src/main/antora/modules/ROOT/pages/query-by-example.adoc +++ b/src/main/antora/modules/ROOT/pages/query-by-example.adoc @@ -1,3 +1,4 @@ +:support-qbe-collection: false include::{commons}@data-commons::query-by-example.adoc[] Here's an example: