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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user