DATAJPA-292 - Manually defined queries can contain like expressions now.
This commit introduces support for JPQL inline like expressions such as "… like ?1%". The implementation will detect those expressions and turn the query back into a valid JPQL one. On query execution the parameters will be bound according to the like expression defined in the query.
This commit is contained in:
@@ -508,6 +508,31 @@ public class User {
|
||||
}</programlisting>
|
||||
</example>
|
||||
|
||||
<simplesect>
|
||||
<title>Using advanced <code>LIKE</code> expressions</title>
|
||||
|
||||
<para>The query execution mechanism for manually defined queries using
|
||||
<interfacename>@Query</interfacename> allow the definition of advanced
|
||||
<code>LIKE</code> expressions inside the query definition.</para>
|
||||
|
||||
<example>
|
||||
<title>Advanced <code>LIKE</code> expressions in
|
||||
<interfacename>@Query</interfacename></title>
|
||||
|
||||
<programlisting language="java">public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
@Query("select u from User u where u.firstname like %?1")
|
||||
List<User> findByFirstnameEndsWith(String firstname);
|
||||
}</programlisting>
|
||||
</example>
|
||||
|
||||
<para>In the just shown sample <code>LIKE</code> delimiter character
|
||||
<code>%</code> is recognized and the query transformed into a valid
|
||||
JPQL query (removing the <code>%</code>). Upon query execution the
|
||||
parameter handed into the method call gets augmented with the
|
||||
previously recognized <code>LIKE</code> pattern.</para>
|
||||
</simplesect>
|
||||
|
||||
<simplesect>
|
||||
<title>Native queries</title>
|
||||
|
||||
|
||||
@@ -37,9 +37,8 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SimpleJpaQuery.class);
|
||||
|
||||
private final String queryString;
|
||||
private final String countQuery;
|
||||
private final String alias;
|
||||
private final StringQuery query;
|
||||
private final StringQuery countQuery;
|
||||
|
||||
private final JpaQueryMethod method;
|
||||
|
||||
@@ -50,11 +49,10 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
super(method, em);
|
||||
|
||||
this.queryString = queryString;
|
||||
this.alias = QueryUtils.detectAlias(queryString);
|
||||
this.countQuery = method.getCountQuery() == null ? QueryUtils.createCountQueryFor(queryString) : method
|
||||
.getCountQuery();
|
||||
this.method = method;
|
||||
this.query = new StringQuery(queryString);
|
||||
this.countQuery = new StringQuery(method.getCountQuery() == null ? QueryUtils.createCountQueryFor(queryString)
|
||||
: method.getCountQuery());
|
||||
|
||||
Parameters parameters = method.getParameters();
|
||||
boolean hasPagingOrSortingParameter = parameters.hasPageableParameter() || parameters.hasSortParameter();
|
||||
@@ -66,7 +64,7 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
|
||||
// Try to create a Query object already to fail fast
|
||||
if (!method.isNativeQuery()) {
|
||||
try {
|
||||
em.createQuery(queryString);
|
||||
em.createQuery(query.getQuery());
|
||||
} catch (RuntimeException e) {
|
||||
// Needed as there's ambiguities in how an invalid query string shall be expressed by the persistence provider
|
||||
// http://java.net/projects/jpa-spec/lists/jsr338-experts/archive/2012-07/message/17
|
||||
@@ -82,6 +80,15 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
|
||||
this(method, em, method.getAnnotatedQuery());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#createBinder(java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
protected ParameterBinder createBinder(Object[] values) {
|
||||
return new StringQueryParameterBinder(getQueryMethod().getParameters(), values, query);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#createQuery(java.lang.Object[])
|
||||
@@ -90,7 +97,7 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
|
||||
public Query doCreateQuery(Object[] values) {
|
||||
|
||||
ParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), values);
|
||||
String sortedQueryString = QueryUtils.applySorting(queryString, accessor.getSort(), alias);
|
||||
String sortedQueryString = QueryUtils.applySorting(query.getQuery(), accessor.getSort(), query.getAlias());
|
||||
EntityManager em = getEntityManager();
|
||||
|
||||
Query query = null;
|
||||
@@ -111,8 +118,7 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
|
||||
*/
|
||||
@Override
|
||||
protected TypedQuery<Long> doCreateCountQuery(Object[] values) {
|
||||
|
||||
return createBinder(values).bind(getEntityManager().createQuery(countQuery, Long.class));
|
||||
return createBinder(values).bind(getEntityManager().createQuery(countQuery.getQuery(), Long.class));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,4 +137,4 @@ final class SimpleJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
return query == null ? null : new SimpleJpaQuery(queryMethod, em, query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static java.util.regex.Pattern.*;
|
||||
import static org.springframework.util.ObjectUtils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Encapsulation of a String JPA query.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class StringQuery {
|
||||
|
||||
private static final Pattern LIKE_PATTERN;
|
||||
private static final String MESSAGE = "Already found like binding with same index / parameter name but differing binding type! Already have: %s, found %s! If you bind a parameter multiple times make sure they use the same like binding.";
|
||||
|
||||
static {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("(?<=like)"); // starts with like
|
||||
builder.append("(?: )+"); // some whitespace
|
||||
builder.append("(");
|
||||
builder.append("%?(\\?(\\d+))%?"); // position parameter with likes
|
||||
builder.append("|"); // or
|
||||
builder.append("%?(:(\\w+))%?"); // named parameter with likes;
|
||||
builder.append(")");
|
||||
LIKE_PATTERN = Pattern.compile(builder.toString(), CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private final String query;
|
||||
private final List<StringQuery.LikeBinding> bindings;
|
||||
private final String alias;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringQuery} from the given JPQL query.
|
||||
*
|
||||
* @param query must not be {@literal null} or empty.
|
||||
*/
|
||||
public StringQuery(String query) {
|
||||
|
||||
Assert.hasText(query, "Query must not be null or empty!");
|
||||
|
||||
this.bindings = new ArrayList<StringQuery.LikeBinding>();
|
||||
this.query = parseLikeBindings(query);
|
||||
this.alias = QueryUtils.detectAlias(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we have found some like bindings.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasLikeBindings() {
|
||||
return !bindings.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LikeBinding}s registered.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<LikeBinding> getLikeBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JPQL query.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main alias used in the query.
|
||||
*
|
||||
* @return the alias
|
||||
*/
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LikeBinding} for the given name.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public LikeBinding getBindingFor(String name) {
|
||||
|
||||
for (StringQuery.LikeBinding binding : bindings) {
|
||||
if (binding.hasName(name)) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LikeBinding} for the given position.
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public LikeBinding getBindingFor(int position) {
|
||||
for (LikeBinding binding : bindings) {
|
||||
if (binding.hasPosition(position)) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses {@link LikeBinding} instances from the given query and adds them to the registered bindings. Returns the
|
||||
* cleaned up query.
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
private final String parseLikeBindings(String query) {
|
||||
|
||||
Matcher matcher = LIKE_PATTERN.matcher(query);
|
||||
String result = query;
|
||||
|
||||
while (matcher.find()) {
|
||||
|
||||
Type likeType = getLikeTypeFrom(matcher.group(1));
|
||||
String index = matcher.group(3);
|
||||
String replacement = matcher.group(2);
|
||||
|
||||
if (index != null) {
|
||||
checkAndRegister(new LikeBinding(Integer.parseInt(index), likeType));
|
||||
} else {
|
||||
checkAndRegister(new LikeBinding(matcher.group(5), likeType));
|
||||
replacement = matcher.group(4);
|
||||
}
|
||||
|
||||
result = StringUtils.replace(result, matcher.group(1), replacement);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private final void checkAndRegister(LikeBinding binding) {
|
||||
|
||||
for (LikeBinding existing : bindings) {
|
||||
if (existing.hasName(binding.name) || existing.hasPosition(binding.position)) {
|
||||
Assert.isTrue(existing.equals(binding), String.format(MESSAGE, existing, binding));
|
||||
}
|
||||
}
|
||||
|
||||
this.bindings.add(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the like {@link Type} from the given JPA like expression.
|
||||
*
|
||||
* @param expression must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
private static Type getLikeTypeFrom(String expression) {
|
||||
|
||||
Assert.hasText(expression);
|
||||
|
||||
if (expression.matches("%.*%")) {
|
||||
return Type.CONTAINING;
|
||||
}
|
||||
|
||||
if (expression.startsWith("%")) {
|
||||
return Type.ENDING_WITH;
|
||||
}
|
||||
|
||||
if (expression.endsWith("%")) {
|
||||
return Type.STARTING_WITH;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Illegal like pattern %s!", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a paramter binding in a JPQL query augmented with instructions of how to apply a parameter as LIKE
|
||||
* parameter. This allows expressions like {@code …like %?1} in the JPQL query, which is not allowed by plain JPA.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class LikeBinding {
|
||||
|
||||
private static final List<Type> SUPPORTED_TYPES = Arrays.asList(Type.CONTAINING, Type.STARTING_WITH,
|
||||
Type.ENDING_WITH);
|
||||
|
||||
private final String name;
|
||||
private final Integer position;
|
||||
private final Type type;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LikeBinding} for the parameter with the given name and {@link Type}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public LikeBinding(String name, Type type) {
|
||||
|
||||
Assert.hasText(name, "Name must not be null or empty!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.isTrue(SUPPORTED_TYPES.contains(type),
|
||||
String.format("Type must be one of %s!", StringUtils.collectionToCommaDelimitedString(SUPPORTED_TYPES)));
|
||||
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.position = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LikeBinding} for the parameter with the given position and {@link Type}.
|
||||
*
|
||||
* @param position
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public LikeBinding(int position, Type type) {
|
||||
|
||||
Assert.isTrue(position > 0, "Position must be greater than zero!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
this.position = position;
|
||||
this.type = type;
|
||||
this.name = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the binding has the given name. Will always be {@literal false} in case the {@link LikeBinding}
|
||||
* has been set up from a position.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public boolean hasName(String name) {
|
||||
return this.position == null && this.name != null && this.name.equals(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the binding has the given position. Will always be {@literal false} in case the
|
||||
* {@link LikeBinding} has been set up from a name.
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public boolean hasPosition(int position) {
|
||||
return this.name == null && this.position == position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the {@link LikeBinding}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the given raw value according to the like type.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public Object prepare(Object value) {
|
||||
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case STARTING_WITH:
|
||||
return String.format("%s%%", value.toString());
|
||||
case ENDING_WITH:
|
||||
return String.format("%%%s", value.toString());
|
||||
case CONTAINING:
|
||||
return String.format("%%%s%%", value.toString());
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (!(obj instanceof LikeBinding)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LikeBinding that = (LikeBinding) obj;
|
||||
|
||||
return nullSafeEquals(this.name, that.name) && nullSafeEquals(this.position, that.position)
|
||||
&& this.type.equals(that.type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int result = 17;
|
||||
|
||||
result += nullSafeHashCode(this.name);
|
||||
result += nullSafeHashCode(this.position);
|
||||
result += nullSafeHashCode(this.type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("LikeBinding [name: %s, position: %d, type: %s]", name, position, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.LikeBinding;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ParameterBinder} that takes {@link LikeBinding}s encapsulated in a {@link StringQuery} into account.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class StringQueryParameterBinder extends ParameterBinder {
|
||||
|
||||
private final StringQuery query;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringQueryParameterBinder} from the given {@link Parameters}, method arguments and
|
||||
* {@link StringQuery}.
|
||||
*
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @param values must not be {@literal null}.
|
||||
* @param query must not be {@literal null}.
|
||||
*/
|
||||
public StringQueryParameterBinder(Parameters parameters, Object[] values, StringQuery query) {
|
||||
|
||||
super(parameters, values);
|
||||
|
||||
Assert.notNull(query, "StringQuery must not be null!");
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.query.ParameterBinder#bind(javax.persistence.Query, org.springframework.data.repository.query.Parameter, java.lang.Object, int)
|
||||
*/
|
||||
@Override
|
||||
protected void bind(Query jpaQuery, Parameter methodParameter, Object value, int position) {
|
||||
|
||||
Object valueToBind = value;
|
||||
|
||||
if (query.hasLikeBindings()) {
|
||||
|
||||
LikeBinding binding = getBindingFor(jpaQuery, position, methodParameter);
|
||||
|
||||
if (binding != null) {
|
||||
valueToBind = binding.prepare(valueToBind);
|
||||
}
|
||||
}
|
||||
|
||||
super.bind(jpaQuery, methodParameter, valueToBind, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@link LikeBinding} to be applied before binding a parameter value to the query.
|
||||
*
|
||||
* @param jpaQuery must not be {@literal null}.
|
||||
* @param position
|
||||
* @param methodParameter must not be {@literal null}.
|
||||
* @return the {@link LikeBinding} for the given parameters or {@literal null} if none available.
|
||||
*/
|
||||
private LikeBinding getBindingFor(Query jpaQuery, int position, Parameter methodParameter) {
|
||||
|
||||
try {
|
||||
|
||||
jpaQuery.getParameter(position);
|
||||
return query.getBindingFor(position);
|
||||
|
||||
} catch (IllegalArgumentException o_O) {
|
||||
|
||||
if (hasNamedParameter(jpaQuery)) {
|
||||
return query.getBindingFor(methodParameter.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -999,6 +999,34 @@ public class UserRepositoryTests {
|
||||
assertThat(page, hasItem(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-292
|
||||
*/
|
||||
@Test
|
||||
public void executesManualQueryWithPositionLikeExpressionCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
List<User> result = repository.findByFirstnameLike("Da");
|
||||
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result, hasItem(thirdUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-292
|
||||
*/
|
||||
@Test
|
||||
public void executesManualQueryWithNamedLikeExpressionCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
List<User> result = repository.findByFirstnameLikeNamed("Da");
|
||||
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result, hasItem(thirdUser));
|
||||
}
|
||||
|
||||
private Page<User> executeSpecWithSort(Sort sort) {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.LikeBinding;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class LikeBindingUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullName() {
|
||||
new LikeBinding(null, Type.CONTAINING);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsEmptyName() {
|
||||
new LikeBinding("", Type.CONTAINING);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullType() {
|
||||
new LikeBinding("foo", null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsInvalidType() {
|
||||
new LikeBinding("foo", Type.SIMPLE_PROPERTY);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsInvalidPosition() {
|
||||
new LikeBinding(0, Type.CONTAINING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setsUpInstanceForName() {
|
||||
|
||||
LikeBinding binding = new LikeBinding("foo", Type.CONTAINING);
|
||||
|
||||
assertThat(binding.hasName("foo"), is(true));
|
||||
assertThat(binding.hasName("bar"), is(false));
|
||||
assertThat(binding.hasName(null), is(false));
|
||||
assertThat(binding.hasPosition(0), is(false));
|
||||
assertThat(binding.getType(), is(Type.CONTAINING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setsUpInstanceForIndex() {
|
||||
|
||||
LikeBinding binding = new LikeBinding(1, Type.CONTAINING);
|
||||
|
||||
assertThat(binding.hasName("foo"), is(false));
|
||||
assertThat(binding.hasName(null), is(false));
|
||||
assertThat(binding.hasPosition(0), is(false));
|
||||
assertThat(binding.hasPosition(1), is(true));
|
||||
assertThat(binding.getType(), is(Type.CONTAINING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void augmentsValueCorrectly() {
|
||||
|
||||
assertAugmentedValue(Type.CONTAINING, "%value%");
|
||||
assertAugmentedValue(Type.ENDING_WITH, "%value");
|
||||
assertAugmentedValue(Type.STARTING_WITH, "value%");
|
||||
|
||||
assertThat(new LikeBinding(1, Type.CONTAINING).prepare(null), is(nullValue()));
|
||||
}
|
||||
|
||||
private static void assertAugmentedValue(Type type, Object value) {
|
||||
|
||||
LikeBinding binding = new LikeBinding("foo", type);
|
||||
assertThat(binding.prepare("value"), is(value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.LikeBinding;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class StringQueryUnitTests {
|
||||
|
||||
@Test
|
||||
public void detectsPositionalLikeBindings() {
|
||||
|
||||
StringQuery query = new StringQuery("select u from User u where u.firstname like %?1% or u.lastname like %?2");
|
||||
|
||||
assertThat(query.hasLikeBindings(), is(true));
|
||||
assertThat(query.getQuery(),
|
||||
is("select u from User u where u.firstname like ?1 or u.lastname like ?2"));
|
||||
|
||||
List<LikeBinding> bindings = query.getLikeBindings();
|
||||
assertThat(bindings, hasSize(2));
|
||||
|
||||
LikeBinding binding = bindings.get(0);
|
||||
assertThat(binding, is(notNullValue()));
|
||||
assertThat(binding.hasPosition(1), is(true));
|
||||
assertThat(binding.getType(), is(Type.CONTAINING));
|
||||
|
||||
binding = bindings.get(1);
|
||||
assertThat(binding, is(notNullValue()));
|
||||
assertThat(binding.hasPosition(2), is(true));
|
||||
assertThat(binding.getType(), is(Type.ENDING_WITH));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsNamedLikeBindings() {
|
||||
|
||||
StringQuery query = new StringQuery("select u from User u where u.firstname like %:firstname");
|
||||
|
||||
assertThat(query.hasLikeBindings(), is(true));
|
||||
assertThat(query.getQuery(), is("select u from User u where u.firstname like :firstname"));
|
||||
|
||||
List<LikeBinding> bindings = query.getLikeBindings();
|
||||
assertThat(bindings, hasSize(1));
|
||||
|
||||
LikeBinding binding = bindings.get(0);
|
||||
assertThat(binding, is(notNullValue()));
|
||||
assertThat(binding.hasName("firstname"), is(true));
|
||||
assertThat(binding.getType(), is(Type.ENDING_WITH));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsDifferentBindingsForRepeatedParameter() {
|
||||
new StringQuery("select u from User u where u.firstname like %?1 and u.lastname like ?1%");
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,18 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
|
||||
|
||||
List<User> findByFirstnameNotIn(Collection<String> firstnames);
|
||||
|
||||
/**
|
||||
* @see DATAJPA-292
|
||||
*/
|
||||
@Query("select u from User u where u.firstname like ?1%")
|
||||
List<User> findByFirstnameLike(String firstname);
|
||||
|
||||
/**
|
||||
* @see DATAJPA-292
|
||||
*/
|
||||
@Query("select u from User u where u.firstname like :firstname%")
|
||||
List<User> findByFirstnameLikeNamed(@Param("firstname") String firstname);
|
||||
|
||||
/**
|
||||
* Manipulating query to set all {@link User}'s names to the given one.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user