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.
This commit is contained in:
Oliver Gierke
2012-04-11 21:49:40 +02:00
parent 26f0eec010
commit f441087cf4
6 changed files with 136 additions and 37 deletions

View File

@@ -253,6 +253,33 @@
<entry><code>… where x.firstname not like ?1</code></entry>
</row>
<row>
<entry><code>StartingWith</code></entry>
<entry><code>findByFirstnameStartingWith</code></entry>
<entry><code>… where x.firstname like ?1</code> (parameter
bound with prepended <code>%</code>)</entry>
</row>
<row>
<entry><code>ngWith</code></entry>
<entry><code>findByFirstnameEndingWith</code></entry>
<entry><code>… where x.firstname like ?1</code> (parameter
bound with appended <code>%</code>)</entry>
</row>
<row>
<entry><code>Containing</code></entry>
<entry><code>findByFirstnameContaining</code></entry>
<entry><code>… where x.firstname like ?1</code> (parameter
bound wrapped in <code>%</code>)</entry>
</row>
<row>
<entry><code>OrderBy</code></entry>

View File

@@ -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<Object> parameterMetadata = (ParameterMetadata<Object>) expressions.next();
ParameterMetadata<Object> metadata = (ParameterMetadata<Object>) expressions.next();
if (parameterMetadata.isIsNullParameter()) {
if (metadata.isIsNullParameter()) {
return;
}
ParameterExpression<Object> 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));
}
}

View File

@@ -223,12 +223,15 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<Object>,
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<String> propertyExpression = upperIfIgnoreCase(getTypedPath(root, part, String.class));
Expression<String> 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<CriteriaQuery<Object>,
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());
}
}

View File

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

View File

@@ -779,6 +779,42 @@ public class UserRepositoryTests {
assertThat(result, hasItems(firstUser, secondUser));
}
/**
* @see DATAJPA-180
*/
@Test
public void executesFinderWithStartingWithCorrectly() {
flushTestUsers();
List<User> result = repository.findByFirstnameStartingWith("Oli");
assertThat(result.size(), is(1));
assertThat(result, hasItem(firstUser));
}
/**
* @see DATAJPA-180
*/
@Test
public void executesFinderWithEndingWithCorrectly() {
flushTestUsers();
List<User> result = repository.findByFirstnameEndingWith("er");
assertThat(result.size(), is(1));
assertThat(result, hasItem(firstUser));
}
/**
* @see DATAJPA-180
*/
@Test
public void executesFinderWithContainingCorrectly() {
flushTestUsers();
List<User> result = repository.findByFirstnameContaining("a");
assertThat(result.size(), is(2));
assertThat(result, hasItems(secondUser, thirdUser));
}
protected void flushTestUsers() {
firstUser = repository.save(firstUser);

View File

@@ -224,4 +224,20 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
* @see DATAJPA-188
*/
List<User> findByCreatedAtAfter(Date date);
/**
* @see DATAJPA-180
*/
List<User> findByFirstnameStartingWith(String firstname);
/**
* @see DATAJPA-180
*/
List<User> findByFirstnameEndingWith(String firstname);
/**
* @see DATAJPA-180
*/
List<User> findByFirstnameContaining(String firstname);
}