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); + }