DATAJPA-564 - Allow SpEL expressions to be the only consumer of query method parameters.

We now allow the parameters of query methods to also be exclusively consumed by expressions within a given query string. Previously this wasn't possible due to to strict checks for parameter usage in JpaQueryMethod.
Relax check for query param usage in JpaQueryMethod.
This commit is contained in:
Thomas Darimont
2014-07-07 16:17:27 +02:00
committed by Oliver Gierke
parent 841bb22906
commit 55f1131b2f
6 changed files with 85 additions and 6 deletions

View File

@@ -107,7 +107,8 @@ public class JpaQueryMethod extends QueryMethod {
continue;
}
if (!annotatedQuery.contains(String.format(":%s", parameter.getName()))) {
if (!annotatedQuery.contains(String.format(":%s", parameter.getName()))
&& !annotatedQuery.contains(String.format("#%s", parameter.getName()))) {
throw new IllegalStateException(String.format(
"Using named parameters for method %s but parameter '%s' not found in annotated query '%s'!", method,
parameter.getName(), annotatedQuery));

View File

@@ -104,7 +104,7 @@ public class ParameterBinder {
for (JpaParameter parameter : parameters) {
if (parameter.isBindable()) {
if (canBindParameter(parameter)) {
Object value = computeParameterValue(parameter, values[methodParameterPosition], values);
@@ -117,6 +117,16 @@ public class ParameterBinder {
return query;
}
/**
* Returns {@literal true} if the given parameter can be bound.
*
* @param parameter
* @return
*/
protected boolean canBindParameter(JpaParameter parameter) {
return parameter.isBindable();
}
/**
* Computes the value to bind for the given {@link JpaParameter} and the given {@code value} by potentially using the
* other {@code values}. This is intended to be customized in sub-classes.

View File

@@ -138,11 +138,11 @@ class StringQuery {
*
* @author Thomas Darimont
*/
private static enum ParameterBindingParser {
public static enum ParameterBindingParser {
INSTANCE;
private static final String EXPRESSION_PREFIX = "__$synthetic$__";
static final String EXPRESSION_PARAMETER_PREFIX = "__$synthetic$__";
private static final Pattern PARAMETER_BINDING_BY_INDEX = Pattern.compile("\\?(\\d+)");
private static final Pattern PARAMETER_BINDING_PATTERN;
private static final String MESSAGE = "Already found parameter binding with same index / parameter name but differing binding type! "
@@ -223,7 +223,7 @@ class StringQuery {
parameterIndex = expressionParameterIndex;
replacement = "?" + parameterIndex;
} else {
parameterName = EXPRESSION_PREFIX + expressionParameterIndex;
parameterName = EXPRESSION_PARAMETER_PREFIX + expressionParameterIndex;
replacement = ":" + parameterName;
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.List;
import javax.persistence.Query;
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
@@ -64,6 +66,34 @@ public class StringQueryParameterBinder extends ExpressionAwareParameterBinder {
super.bind(jpaQuery, methodParameter, binding.prepare(value), position);
}
/* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.ParameterBinder#canBindParameter(org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter)
*/
@Override
protected boolean canBindParameter(JpaParameter parameter) {
List<ParameterBinding> parameterBindings = query.getParameterBindings();
// if no parameter bindings are present, we simply rely on the check in super.
if (parameterBindings.isEmpty()) {
return super.canBindParameter(parameter);
}
// otherwise determine whether there are any non expression parameters left to be bound.
int expressionParameterCount = 0;
for (ParameterBinding binding : parameterBindings) {
if (binding.isExpression()) {
expressionParameterCount++;
}
}
boolean allParametersAreUsedInExpressions = parameterBindings.size() - expressionParameterCount == 0;
// if all parameters are used in expressions, then we can skip their bindings now, since they'll get bound later.
return !allParametersAreUsedInExpressions && super.canBindParameter(parameter);
}
/**
* Finds the {@link LikeParameterBinding} to be applied before binding a parameter value to the query.
*

View File

@@ -58,8 +58,8 @@ import org.springframework.data.jpa.domain.sample.Address;
import org.springframework.data.jpa.domain.sample.Role;
import org.springframework.data.jpa.domain.sample.SpecialUser;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.jpa.repository.sample.UserRepository;
import org.springframework.data.jpa.repository.sample.SampleSecurity.SampleSecurityContextHolder;
import org.springframework.data.jpa.repository.sample.UserRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
@@ -1706,6 +1706,32 @@ public class UserRepositoryTests {
assertThat(users.get(0), is(secondUser));
}
/**
* @see DATAJPA-564
*/
@Test
public void shouldfindUsersByFirstnameForSpELExpressionOnlyWithParameterNameVariableReference() {
flushTestUsers();
List<User> users = repository.findUsersByFirstnameForSpELExpressionWithParameterVariableOnly("Joachim");
assertThat(users, hasSize(1));
assertThat(users.get(0), is(secondUser));
}
/**
* @see DATAJPA-564
*/
@Test
public void shouldfindUsersByFirstnameForSpELExpressionOnlyWithParameterIndexReference() {
flushTestUsers();
List<User> users = repository.findUsersByFirstnameForSpELExpressionWithParameterIndexOnly("Joachim");
assertThat(users, hasSize(1));
assertThat(users.get(0), is(secondUser));
}
private Page<User> executeSpecWithSort(Sort sort) {
flushTestUsers();

View File

@@ -507,4 +507,16 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
*/
@Query("select u from User u where u.firstname = ?1 and u.firstname=?#{[0]} and u.emailAddress = ?#{principal.emailAddress}")
List<User> findByFirstnameAndCurrentUserWithCustomQuery(String firstname);
/**
* @see DATAJPA-564
*/
@Query("select u from User u where u.firstname = :#{#firstname}")
List<User> findUsersByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname);
/**
* @see DATAJPA-564
*/
@Query("select u from User u where u.firstname = ?#{[0]}")
List<User> findUsersByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname);
}