From 34071c4a8150291b0d0d4d4effbd500a7f0d3ab8 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 9 Jul 2018 11:32:01 +0200 Subject: [PATCH] DATAJPA-1372 - Allow Query by Example matching for joined singular attributes. We now support Query by Example matching for singular attributes that are joined through embeddable entities. Previously, we rejected these as the metamodel returns a SingularAttributeJoin whereas we always expect a From to create a join. Original pull request: #281. --- .../QueryByExamplePredicateBuilder.java | 8 +-- ...eryByExamplePredicateBuilderUnitTests.java | 60 +++++++++++++------ 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java b/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java index 7fc18398b..251940af7 100644 --- a/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java +++ b/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java @@ -149,7 +149,8 @@ public class QueryByExamplePredicateBuilder { Object attributeValue = optionalValue.get(); - if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.EMBEDDED)) { + if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.EMBEDDED) + || (isAssociation(attribute) && !(from instanceof From))) { predicates .addAll(getPredicates(currentPath, cb, from.get(attribute.getName()), (ManagedType) attribute.getType(), @@ -159,11 +160,6 @@ public class QueryByExamplePredicateBuilder { if (isAssociation(attribute)) { - if (!(from instanceof From)) { - throw new JpaSystemException(new IllegalArgumentException(String - .format("Unexpected path type for %s. Found %s where From.class was expected.", currentPath, from))); - } - PathNode node = currentNode.add(attribute.getName(), attributeValue); if (node.spansCycle()) { throw new InvalidDataAccessApiUsageException( diff --git a/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java b/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java index 5b1e66964..dee94d559 100644 --- a/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilderUnitTests.java @@ -27,6 +27,8 @@ import java.util.Set; import javax.persistence.Id; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.Expression; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; @@ -46,6 +48,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.util.ObjectUtils; @@ -64,11 +67,14 @@ public class QueryByExamplePredicateBuilderUnitTests { @Mock CriteriaBuilder cb; @Mock Root root; @Mock EntityType personEntityType; + @Mock EntityType skillEntityType; @Mock Expression expressionMock; @Mock Predicate truePredicate, dummyPredicate, andPredicate, orPredicate; @Mock Path dummyPath; + @Mock Join from; Set> personEntityAttribtues; + Set> skillEntityAttribtues; SingularAttribute personIdAttribute; SingularAttribute personFirstnameAttribute; @@ -76,6 +82,8 @@ public class QueryByExamplePredicateBuilderUnitTests { SingularAttribute personFatherAttribute; SingularAttribute personSkillAttribute; SingularAttribute personAddressAttribute; + SingularAttribute skillNameAttribute; + SingularAttribute skillNestedAttribute; public @Rule ExpectedException exception = ExpectedException.none(); @@ -88,10 +96,14 @@ public class QueryByExamplePredicateBuilderUnitTests { personAgeAttribute = new SingluarAttributeStub<>("age", PersistentAttributeType.BASIC, Long.class); personFatherAttribute = new SingluarAttributeStub<>("father", PersistentAttributeType.MANY_TO_ONE, Person.class, personEntityType); - personSkillAttribute = new SingluarAttributeStub<>("skill", PersistentAttributeType.MANY_TO_ONE, - Skill.class); + personSkillAttribute = new SingluarAttributeStub<>("skill", PersistentAttributeType.EMBEDDED, + Skill.class, skillEntityType); personAddressAttribute = new SingluarAttributeStub<>("address", PersistentAttributeType.EMBEDDED, Address.class); + skillNameAttribute = new SingluarAttributeStub("name", PersistentAttributeType.BASIC, + String.class); + skillNestedAttribute = new SingluarAttributeStub<>("nested", PersistentAttributeType.MANY_TO_ONE, + Skill.class, skillEntityType); personEntityAttribtues = new LinkedHashSet<>(); personEntityAttribtues.add(personIdAttribute); @@ -101,14 +113,21 @@ public class QueryByExamplePredicateBuilderUnitTests { personEntityAttribtues.add(personAddressAttribute); personEntityAttribtues.add(personSkillAttribute); + skillEntityAttribtues = new LinkedHashSet<>(); + skillEntityAttribtues.add(skillNameAttribute); + skillEntityAttribtues.add(skillNestedAttribute); + doReturn(dummyPath).when(root).get(any(SingularAttribute.class)); + doReturn(dummyPath).when(root).get(anyString()); doReturn(personEntityType).when(root).getModel(); doReturn(personEntityAttribtues).when(personEntityType).getSingularAttributes(); + doReturn(skillEntityAttribtues).when(skillEntityType).getSingularAttributes(); + doReturn(dummyPredicate).when(cb).equal(any(Expression.class), any(String.class)); doReturn(dummyPredicate).when(cb).equal(any(Expression.class), any(Long.class)); - doReturn(dummyPredicate).when(cb).like(any(Expression.class), any(String.class)); + doReturn(dummyPredicate).when(cb).like(any(Expression.class), any(String.class), anyChar()); doReturn(expressionMock).when(cb).literal(any(Boolean.class)); doReturn(truePredicate).when(cb).isTrue(eq(expressionMock)); @@ -148,20 +167,6 @@ public class QueryByExamplePredicateBuilderUnitTests { verify(cb, times(1)).equal(any(Expression.class), eq("foo")); } - @Test // DATAJPA-937 - public void unresolvableNestedAssociatedPathShouldFail() { - - Person p = new Person(); - Person father = new Person(); - father.father = new Person(); - p.father = father; - - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> QueryByExamplePredicateBuilder.getPredicate(root, cb, of(p), EscapeCharacter.DEFAULT)) - .withCauseInstanceOf(IllegalArgumentException.class) - .withMessageContaining("Unexpected path type"); - } - @Test // DATAJPA-218 public void multiPredicateCriteriaShouldReturnCombinedOnes() { @@ -191,6 +196,26 @@ public class QueryByExamplePredicateBuilderUnitTests { verify(cb, times(1)).or(ArgumentMatchers.any()); } + @Test // DATAJPA-1372 + public void considersSingularJoinedAttributes() { + + doReturn(from).when(root).join(anyString()); + doReturn(dummyPath).when(dummyPath).get(any(SingularAttribute.class)); + doReturn(dummyPath).when(dummyPath).get(anyString()); + + Person person = new Person(); + person.skill = new Skill(); + person.skill.nested = new Skill(); + person.skill.nested.name = "foo"; + + Example example = of(person, + ExampleMatcher.matching().withMatcher("skill.nested.name", GenericPropertyMatcher::contains)); + + assertThat(QueryByExamplePredicateBuilder.getPredicate(root, cb, example)).isEqualTo(dummyPredicate); + + verify(cb).like(dummyPath, "%foo%", '\\'); + } + @Test // DATAJPA-1534 public void likePatternsGetEscapedContaining() { @@ -270,6 +295,7 @@ public class QueryByExamplePredicateBuilderUnitTests { @Id Long id; String name; + Skill nested; } static class SingluarAttributeStub implements SingularAttribute {