From f441087cf4681a3edb1044568f6c6bcb3b3beee2 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 11 Apr 2012 21:49:40 +0200 Subject: [PATCH] DATAJPA-180 - Added support for StartingWith, EndingWith, Containing to query creator. The query derivation mechanism now supports StartingWith, EndingWith and Containing as keywords using the Criteria API's like predicate and massaging the given parameters accordingly. Refactored parameter binding for CriteriaQuery instances to encapsulate the knowledge of how to massage a parameter based on the type. --- src/docbkx/reference/jpa.xml | 27 ++++++++++ .../query/CriteriaQueryParameterBinder.java | 38 ++------------ .../jpa/repository/query/JpaQueryCreator.java | 7 ++- .../query/ParameterMetadataProvider.java | 49 +++++++++++++++++++ .../jpa/repository/UserRepositoryTests.java | 36 ++++++++++++++ .../jpa/repository/sample/UserRepository.java | 16 ++++++ 6 files changed, 136 insertions(+), 37 deletions(-) diff --git a/src/docbkx/reference/jpa.xml b/src/docbkx/reference/jpa.xml index cc35725a9..70d640321 100644 --- a/src/docbkx/reference/jpa.xml +++ b/src/docbkx/reference/jpa.xml @@ -253,6 +253,33 @@ … where x.firstname not like ?1 + + StartingWith + + findByFirstnameStartingWith + + … where x.firstname like ?1 (parameter + bound with prepended %) + + + + ngWith + + findByFirstnameEndingWith + + … where x.firstname like ?1 (parameter + bound with appended %) + + + + Containing + + findByFirstnameContaining + + … where x.firstname like ?1 (parameter + bound wrapped in %) + + OrderBy diff --git a/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java b/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java index 9f61500fb..11cdf3f81 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/CriteriaQueryParameterBinder.java @@ -15,10 +15,6 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import javax.persistence.Query; @@ -28,7 +24,6 @@ import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.P import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * Special {@link ParameterBinder} that uses {@link ParameterExpression}s to bind query parameters. @@ -64,39 +59,12 @@ class CriteriaQueryParameterBinder extends ParameterBinder { @SuppressWarnings("unchecked") protected void bind(Query query, Parameter parameter, Object value, int position) { - ParameterMetadata parameterMetadata = (ParameterMetadata) expressions.next(); + ParameterMetadata metadata = (ParameterMetadata) expressions.next(); - if (parameterMetadata.isIsNullParameter()) { + if (metadata.isIsNullParameter()) { return; } - ParameterExpression expression = parameterMetadata.getExpression(); - - Object valueToBind = Collection.class.equals(expression.getJavaType()) ? toCollection(value) : value; - query.setParameter(expression, valueToBind); - } - - /** - * Return sthe given argument as {@link Collection} which means it will return it as is if it's a {@link Collections}, - * turn an array into an {@link ArrayList} or simply wrap any other value into a single element {@link Collections}. - * - * @param value - * @return - */ - private static Collection toCollection(Object value) { - - if (value == null) { - return null; - } - - if (value instanceof Collection) { - return (Collection) value; - } - - if (ObjectUtils.isArray(value)) { - return Arrays.asList(ObjectUtils.toObjectArray(value)); - } - - return Collections.singleton(value); + query.setParameter(metadata.getExpression(), metadata.prepare(value)); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 08514ecfe..92e51414a 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -223,12 +223,15 @@ public class JpaQueryCreator extends AbstractQueryCreator, return path.in(provider.next(part, Collection.class).getExpression()).not(); case IN: return path.in(provider.next(part, Collection.class).getExpression()); + case STARTING_WITH: + case ENDING_WITH: + case CONTAINING: case LIKE: case NOT_LIKE: Expression propertyExpression = upperIfIgnoreCase(getTypedPath(root, part, String.class)); Expression parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression()); Predicate like = builder.like(propertyExpression, parameterExpression); - return part.getType() == Type.LIKE ? like : like.not(); + return part.getType() == Type.NOT_LIKE ? like.not() : like; case TRUE: return builder.isTrue(getTypedPath(root, part, Boolean.class)); case FALSE: @@ -240,7 +243,7 @@ public class JpaQueryCreator extends AbstractQueryCreator, case NEGATING_SIMPLE_PROPERTY: return builder.notEqual(upperIfIgnoreCase(path), upperIfIgnoreCase(provider.next(part).getExpression())); default: - throw new IllegalArgumentException("Unsupported keyword + " + part.getType()); + throw new IllegalArgumentException("Unsupported keyword " + part.getType()); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index f56ab5935..ddf15ddca 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -1,10 +1,13 @@ package org.springframework.data.jpa.repository.query; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.ParameterExpression; @@ -15,6 +18,7 @@ import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; /** * Helper class to allow easy creation of {@link ParameterMetadata}s. @@ -141,5 +145,50 @@ class ParameterMetadataProvider { public boolean isIsNullParameter() { return Type.IS_NULL.equals(type); } + + /** + * Prepares the object before it's actually bound to the {@link Query}. + * + * @param parameter + * @return + */ + public Object prepare(Object parameter) { + + switch (type) { + case STARTING_WITH: + return String.format("%s%%", parameter.toString()); + case ENDING_WITH: + return String.format("%%%s", parameter.toString()); + case CONTAINING: + return String.format("%%%s%%", parameter.toString()); + default: + return Collection.class.equals(expression.getJavaType()) ? toCollection(parameter) : parameter; + } + } + + /** + * Return sthe given argument as {@link Collection} which means it will return it as is if it's a + * {@link Collections}, turn an array into an {@link ArrayList} or simply wrap any other value into a single element + * {@link Collections}. + * + * @param value + * @return + */ + private static Collection toCollection(Object value) { + + if (value == null) { + return null; + } + + if (value instanceof Collection) { + return (Collection) value; + } + + if (ObjectUtils.isArray(value)) { + return Arrays.asList(ObjectUtils.toObjectArray(value)); + } + + return Collections.singleton(value); + } } } \ No newline at end of file diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 8d7d7c1cb..76317144f 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -779,6 +779,42 @@ public class UserRepositoryTests { assertThat(result, hasItems(firstUser, secondUser)); } + /** + * @see DATAJPA-180 + */ + @Test + public void executesFinderWithStartingWithCorrectly() { + + flushTestUsers(); + List result = repository.findByFirstnameStartingWith("Oli"); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(firstUser)); + } + + /** + * @see DATAJPA-180 + */ + @Test + public void executesFinderWithEndingWithCorrectly() { + + flushTestUsers(); + List result = repository.findByFirstnameEndingWith("er"); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(firstUser)); + } + + /** + * @see DATAJPA-180 + */ + @Test + public void executesFinderWithContainingCorrectly() { + + flushTestUsers(); + List result = repository.findByFirstnameContaining("a"); + assertThat(result.size(), is(2)); + assertThat(result, hasItems(secondUser, thirdUser)); + } + protected void flushTestUsers() { firstUser = repository.save(firstUser); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 56e35aa54..5b6821e82 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -224,4 +224,20 @@ public interface UserRepository extends JpaRepository, JpaSpecifi * @see DATAJPA-188 */ List findByCreatedAtAfter(Date date); + + /** + * @see DATAJPA-180 + */ + List findByFirstnameStartingWith(String firstname); + + /** + * @see DATAJPA-180 + */ + List findByFirstnameEndingWith(String firstname); + + /** + * @see DATAJPA-180 + */ + List findByFirstnameContaining(String firstname); + }