DATAJPA-461 - Fixed regression in parameter binding of arrays.
Enhanced binding of parameters in StringQueryParameterBinder to be able to deal with situations where a parameter value has to be converted to be correctly bound e.g. for parameter values in IN-expressions. We now only convert array values to collections if the value is to be bound in the context of an IN-parameter. Previously we erroneously always converted an array value to a collection value which lead to problems if an array value was meant to be used "as-is" e.g. in cases where an user wants to query for a certain byte[]. Original pull request: #56.
This commit is contained in:
committed by
Oliver Gierke
parent
c85caf00d4
commit
f8b0917c90
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2013 the original author or authors.
|
||||
* Copyright 2008-2014 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.
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Query;
|
||||
@@ -27,7 +24,6 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link ParameterBinder} is used to bind method parameters to a {@link Query}. This is usually done whenever an
|
||||
@@ -132,7 +128,7 @@ public class ParameterBinder {
|
||||
return;
|
||||
}
|
||||
|
||||
Object valueToUse = convertArrayToCollectionIfNecessary(value);
|
||||
Object valueToUse = value;
|
||||
|
||||
if (hasNamedParameter(query) && parameter.isNamedParameter()) {
|
||||
query.setParameter(parameter.getName(), valueToUse);
|
||||
@@ -141,27 +137,6 @@ public class ParameterBinder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given value as collection if it is an array or as is if not.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Object convertArrayToCollectionIfNecessary(Object value) {
|
||||
|
||||
if (!ObjectUtils.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
int length = Array.getLength(value);
|
||||
Collection<Object> result = new ArrayList<Object>(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
result.add(Array.get(value, i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the parameters to the given query and applies special parameter types (e.g. pagination).
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -18,14 +18,17 @@ package org.springframework.data.jpa.repository.query;
|
||||
import static java.util.regex.Pattern.*;
|
||||
import static org.springframework.util.ObjectUtils.*;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
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.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -36,24 +39,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
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 List<ParameterBinding> bindings;
|
||||
private final String alias;
|
||||
|
||||
/**
|
||||
@@ -65,8 +52,9 @@ class StringQuery {
|
||||
|
||||
Assert.hasText(query, "Query must not be null or empty!");
|
||||
|
||||
this.bindings = new ArrayList<StringQuery.LikeBinding>();
|
||||
this.query = parseLikeBindings(query);
|
||||
this.bindings = new ArrayList<StringQuery.ParameterBinding>();
|
||||
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
|
||||
this.bindings);
|
||||
this.alias = QueryUtils.detectAlias(query);
|
||||
}
|
||||
|
||||
@@ -75,16 +63,16 @@ class StringQuery {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasLikeBindings() {
|
||||
public boolean hasParameterBindings() {
|
||||
return !bindings.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LikeBinding}s registered.
|
||||
* Returns the {@link ParameterBinding}s registered.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<LikeBinding> getLikeBindings() {
|
||||
List<ParameterBinding> getParameterBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
@@ -107,14 +95,14 @@ class StringQuery {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LikeBinding} for the given name.
|
||||
* Returns the {@link ParameterBinding} for the given name.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public LikeBinding getBindingFor(String name) {
|
||||
public ParameterBinding getBindingFor(String name) {
|
||||
|
||||
for (StringQuery.LikeBinding binding : bindings) {
|
||||
for (StringQuery.ParameterBinding binding : bindings) {
|
||||
if (binding.hasName(name)) {
|
||||
return binding;
|
||||
}
|
||||
@@ -124,13 +112,13 @@ class StringQuery {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LikeBinding} for the given position.
|
||||
* Returns the {@link ParameterBinding} for the given position.
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public LikeBinding getBindingFor(int position) {
|
||||
for (LikeBinding binding : bindings) {
|
||||
public ParameterBinding getBindingFor(int position) {
|
||||
for (ParameterBinding binding : bindings) {
|
||||
if (binding.hasPosition(position)) {
|
||||
return binding;
|
||||
}
|
||||
@@ -140,124 +128,166 @@ class StringQuery {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses {@link LikeBinding} instances from the given query and adds them to the registered bindings. Returns the
|
||||
* cleaned up query.
|
||||
* A parser that extracts the parameter bindings from a given query string.
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
private final String parseLikeBindings(String query) {
|
||||
static enum ParameterBindingParser {
|
||||
|
||||
Matcher matcher = LIKE_PATTERN.matcher(query);
|
||||
String result = query;
|
||||
INSTANCE;
|
||||
|
||||
while (matcher.find()) {
|
||||
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! "
|
||||
+ "Already have: %s, found %s! If you bind a parameter multiple times make sure they use the same binding.";
|
||||
|
||||
Type likeType = getLikeTypeFrom(matcher.group(1));
|
||||
String index = matcher.group(3);
|
||||
String replacement = matcher.group(2);
|
||||
static {
|
||||
|
||||
if (index != null) {
|
||||
checkAndRegister(new LikeBinding(Integer.parseInt(index), likeType));
|
||||
} else {
|
||||
checkAndRegister(new LikeBinding(matcher.group(5), likeType));
|
||||
replacement = matcher.group(4);
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("(?<=(like|in))"); // parameterBindingType -> starts with "like" or "in"
|
||||
builder.append("(?: )+"); // some whitespace
|
||||
builder.append("(");
|
||||
builder.append("%?(\\?(\\d+))%?"); // position parameter
|
||||
builder.append("|"); // or
|
||||
builder.append("%?(:(\\w+))%?"); // named parameter;
|
||||
builder.append(")");
|
||||
|
||||
result = StringUtils.replace(result, matcher.group(1), replacement);
|
||||
PARAMETER_BINDING_PATTERN = Pattern.compile(builder.toString(), CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Parses {@link ParameterBinding} instances from the given query and adds them to the registered bindings. Returns
|
||||
* the cleaned up query.
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
private final String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query,
|
||||
List<ParameterBinding> bindings) {
|
||||
|
||||
private final void checkAndRegister(LikeBinding binding) {
|
||||
Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(query);
|
||||
String result = query;
|
||||
|
||||
for (LikeBinding existing : bindings) {
|
||||
if (existing.hasName(binding.name) || existing.hasPosition(binding.position)) {
|
||||
Assert.isTrue(existing.equals(binding), String.format(MESSAGE, existing, binding));
|
||||
while (matcher.find()) {
|
||||
|
||||
String parameterIndexString = matcher.group(4);
|
||||
String parameterName = parameterIndexString != null ? null : matcher.group(6);
|
||||
Integer parameterIndex = parameterIndexString == null ? null : Integer.valueOf(parameterIndexString);
|
||||
|
||||
switch (ParameterBindingKind.of(matcher.group(1))) {
|
||||
case LIKE:
|
||||
|
||||
Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2));
|
||||
String replacement = matcher.group(3);
|
||||
|
||||
if (parameterIndex != null) {
|
||||
checkAndRegister(new LikeParameterBinding(parameterIndex, likeType), bindings);
|
||||
} else {
|
||||
checkAndRegister(new LikeParameterBinding(parameterName, likeType), bindings);
|
||||
replacement = matcher.group(5);
|
||||
}
|
||||
|
||||
result = StringUtils.replace(result, matcher.group(2), replacement);
|
||||
|
||||
break;
|
||||
|
||||
case IN:
|
||||
|
||||
if (parameterIndex != null) {
|
||||
checkAndRegister(new InParameterBinding(parameterIndex), bindings);
|
||||
} else {
|
||||
checkAndRegister(new InParameterBinding(parameterName), bindings);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AS_IS: // fall-through we don't need a special parameter binding for the given parameter.
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
this.bindings.add(binding);
|
||||
private static void checkAndRegister(ParameterBinding binding, List<ParameterBinding> bindings) {
|
||||
|
||||
for (ParameterBinding existing : bindings) {
|
||||
if (existing.hasName(binding.getName()) || existing.hasPosition(binding.getPosition())) {
|
||||
Assert.isTrue(existing.equals(binding), String.format(MESSAGE, existing, binding));
|
||||
}
|
||||
}
|
||||
|
||||
bindings.add(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum for the supported parameter binding kinds.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
enum ParameterBindingKind {
|
||||
|
||||
LIKE, IN, AS_IS;
|
||||
|
||||
/**
|
||||
* Return the appropriate {@link ParameterBindingKind} for the given {@link String}. Returns {@value #AS_IS} in
|
||||
* case no other {@link ParameterBindingKind} could be found.
|
||||
*
|
||||
* @param parameterBindingKindName
|
||||
* @return
|
||||
*/
|
||||
static ParameterBindingKind of(String parameterBindingKindName) {
|
||||
|
||||
for (ParameterBindingKind type : values()) {
|
||||
if (type.name().equalsIgnoreCase(parameterBindingKindName)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return AS_IS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the like {@link Type} from the given JPA like expression.
|
||||
* A generic parameter binding with name or position information.
|
||||
*
|
||||
* @param expression must not be {@literal null} or empty.
|
||||
* @return
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
return Type.LIKE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Type.LIKE);
|
||||
static class ParameterBinding {
|
||||
|
||||
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}.
|
||||
* Creates a new {@link ParameterBinding} for the parameter with the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param name must not be {@literal null}.
|
||||
*/
|
||||
public LikeBinding(String name, Type type) {
|
||||
public ParameterBinding(String name) {
|
||||
|
||||
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)));
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.position = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LikeBinding} for the parameter with the given position and {@link Type}.
|
||||
* Creates a new {@link ParameterBinding} for the parameter with the given position.
|
||||
*
|
||||
* @param position
|
||||
* @param type must not be {@literal null}.
|
||||
* @param position must not be {@literal null}.
|
||||
*/
|
||||
public LikeBinding(int position, Type type) {
|
||||
public ParameterBinding(Integer position) {
|
||||
|
||||
Assert.isTrue(position > 0, "Position must be greater than zero!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(position, "Position must not be null!");
|
||||
|
||||
this.position = position;
|
||||
this.type = type;
|
||||
this.name = null;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Returns whether the binding has the given name. Will always be {@literal false} in case the
|
||||
* {@link ParameterBinding} has been set up from a position.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
@@ -268,7 +298,7 @@ class StringQuery {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@link ParameterBinding} has been set up from a name.
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
@@ -278,7 +308,171 @@ class StringQuery {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the {@link LikeBinding}.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the position
|
||||
*/
|
||||
public Integer getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int result = 17;
|
||||
|
||||
result += nullSafeHashCode(this.name);
|
||||
result += nullSafeHashCode(this.position);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (!(obj instanceof ParameterBinding)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ParameterBinding that = (ParameterBinding) obj;
|
||||
|
||||
return nullSafeEquals(this.name, that.name) && nullSafeEquals(this.position, that.position);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ParameterBinding [name: %s, position: %d]", getName(), getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param valueToBind
|
||||
* @return
|
||||
*/
|
||||
public Object prepare(Object valueToBind) {
|
||||
return valueToBind;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a {@link ParameterBinding} in a JPQL query augmented with instructions of how to apply a parameter as an
|
||||
* {@code IN} parameter.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
static class InParameterBinding extends ParameterBinding {
|
||||
|
||||
/**
|
||||
* Creates a new {@link InParameterBinding} for the parameter with the given name.
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public InParameterBinding(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link InParameterBinding} for the parameter with the given position.
|
||||
*
|
||||
* @param position
|
||||
*/
|
||||
public InParameterBinding(int position) {
|
||||
super(position);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding#prepare(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object prepare(Object valueToBind) {
|
||||
return convertArrayToCollectionIfNecessary(valueToBind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given value as collection if it is an array or as is if not.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Object convertArrayToCollectionIfNecessary(Object value) {
|
||||
|
||||
if (!ObjectUtils.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
int length = Array.getLength(value);
|
||||
Collection<Object> result = new ArrayList<Object>(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
result.add(Array.get(value, i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a parameter 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
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
static class LikeParameterBinding extends ParameterBinding {
|
||||
|
||||
private static final List<Type> SUPPORTED_TYPES = Arrays.asList(Type.CONTAINING, Type.STARTING_WITH,
|
||||
Type.ENDING_WITH, Type.LIKE);
|
||||
|
||||
private final Type type;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LikeParameterBinding} 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 LikeParameterBinding(String name, Type type) {
|
||||
|
||||
super(name);
|
||||
|
||||
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.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link LikeParameterBinding} for the parameter with the given position and {@link Type}.
|
||||
*
|
||||
* @param position
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public LikeParameterBinding(int position, Type type) {
|
||||
|
||||
super(position);
|
||||
|
||||
Assert.isTrue(position > 0, "Position must be greater than zero!");
|
||||
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.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the {@link LikeParameterBinding}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -317,14 +511,13 @@ class StringQuery {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (!(obj instanceof LikeBinding)) {
|
||||
if (!(obj instanceof LikeParameterBinding)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LikeBinding that = (LikeBinding) obj;
|
||||
LikeParameterBinding that = (LikeParameterBinding) obj;
|
||||
|
||||
return nullSafeEquals(this.name, that.name) && nullSafeEquals(this.position, that.position)
|
||||
&& this.type.equals(that.type);
|
||||
return super.equals(obj) && this.type.equals(that.type);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -334,10 +527,8 @@ class StringQuery {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int result = 17;
|
||||
int result = super.hashCode();
|
||||
|
||||
result += nullSafeHashCode(this.name);
|
||||
result += nullSafeHashCode(this.position);
|
||||
result += nullSafeHashCode(this.type);
|
||||
|
||||
return result;
|
||||
@@ -349,7 +540,32 @@ class StringQuery {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("LikeBinding [name: %s, position: %d, type: %s]", name, position, type);
|
||||
return String.format("LikeBinding [name: %s, position: %d, type: %s]", getName(), getPosition(), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
return Type.LIKE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -18,13 +18,14 @@ package org.springframework.data.jpa.repository.query;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.LikeBinding;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
|
||||
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.
|
||||
* {@link ParameterBinder} that takes {@link LikeParameterBinding}s encapsulated in a {@link StringQuery} into account.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
@@ -58,9 +59,9 @@ public class StringQueryParameterBinder extends ParameterBinder {
|
||||
|
||||
Object valueToBind = value;
|
||||
|
||||
if (query.hasLikeBindings()) {
|
||||
if (query.hasParameterBindings()) {
|
||||
|
||||
LikeBinding binding = getBindingFor(jpaQuery, position, methodParameter);
|
||||
ParameterBinding binding = getBindingFor(jpaQuery, position, methodParameter);
|
||||
|
||||
if (binding != null) {
|
||||
valueToBind = binding.prepare(valueToBind);
|
||||
@@ -71,14 +72,14 @@ public class StringQueryParameterBinder extends ParameterBinder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@link LikeBinding} to be applied before binding a parameter value to the query.
|
||||
* Finds the {@link LikeParameterBinding} 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.
|
||||
* @return the {@link LikeParameterBinding} for the given parameters or {@literal null} if none available.
|
||||
*/
|
||||
private LikeBinding getBindingFor(Query jpaQuery, int position, Parameter methodParameter) {
|
||||
private ParameterBinding getBindingFor(Query jpaQuery, int position, Parameter methodParameter) {
|
||||
|
||||
try {
|
||||
|
||||
|
||||
@@ -1212,6 +1212,34 @@ public class UserRepositoryTests {
|
||||
assertThat(result, hasItem(firstUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @DATAJPA-461
|
||||
*/
|
||||
@Test
|
||||
public void customFindByQueryWithPositionalVarargsParameters() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
Collection<User> result = repository.findByIdsCustomWithPositionalVarArgs(firstUser.getId(), secondUser.getId());
|
||||
|
||||
assertThat(result, hasSize(2));
|
||||
assertThat(result, hasItems(firstUser, secondUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @DATAJPA-461
|
||||
*/
|
||||
@Test
|
||||
public void customFindByQueryWithNamedVarargsParameters() {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
Collection<User> result = repository.findByIdsCustomWithNamedVarArgs(firstUser.getId(), secondUser.getId());
|
||||
|
||||
assertThat(result, hasSize(2));
|
||||
assertThat(result, hasItems(firstUser, secondUser));
|
||||
}
|
||||
|
||||
private Page<User> executeSpecWithSort(Sort sort) {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -19,7 +19,7 @@ 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.jpa.repository.query.StringQuery.LikeParameterBinding;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
|
||||
/**
|
||||
@@ -29,33 +29,33 @@ public class LikeBindingUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullName() {
|
||||
new LikeBinding(null, Type.CONTAINING);
|
||||
new LikeParameterBinding(null, Type.CONTAINING);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsEmptyName() {
|
||||
new LikeBinding("", Type.CONTAINING);
|
||||
new LikeParameterBinding("", Type.CONTAINING);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullType() {
|
||||
new LikeBinding("foo", null);
|
||||
new LikeParameterBinding("foo", null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsInvalidType() {
|
||||
new LikeBinding("foo", Type.SIMPLE_PROPERTY);
|
||||
new LikeParameterBinding("foo", Type.SIMPLE_PROPERTY);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsInvalidPosition() {
|
||||
new LikeBinding(0, Type.CONTAINING);
|
||||
new LikeParameterBinding(0, Type.CONTAINING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setsUpInstanceForName() {
|
||||
|
||||
LikeBinding binding = new LikeBinding("foo", Type.CONTAINING);
|
||||
LikeParameterBinding binding = new LikeParameterBinding("foo", Type.CONTAINING);
|
||||
|
||||
assertThat(binding.hasName("foo"), is(true));
|
||||
assertThat(binding.hasName("bar"), is(false));
|
||||
@@ -67,7 +67,7 @@ public class LikeBindingUnitTests {
|
||||
@Test
|
||||
public void setsUpInstanceForIndex() {
|
||||
|
||||
LikeBinding binding = new LikeBinding(1, Type.CONTAINING);
|
||||
LikeParameterBinding binding = new LikeParameterBinding(1, Type.CONTAINING);
|
||||
|
||||
assertThat(binding.hasName("foo"), is(false));
|
||||
assertThat(binding.hasName(null), is(false));
|
||||
@@ -83,12 +83,12 @@ public class LikeBindingUnitTests {
|
||||
assertAugmentedValue(Type.ENDING_WITH, "%value");
|
||||
assertAugmentedValue(Type.STARTING_WITH, "value%");
|
||||
|
||||
assertThat(new LikeBinding(1, Type.CONTAINING).prepare(null), is(nullValue()));
|
||||
assertThat(new LikeParameterBinding(1, Type.CONTAINING).prepare(null), is(nullValue()));
|
||||
}
|
||||
|
||||
private static void assertAugmentedValue(Type type, Object value) {
|
||||
|
||||
LikeBinding binding = new LikeBinding("foo", type);
|
||||
LikeParameterBinding binding = new LikeParameterBinding("foo", type);
|
||||
assertThat(binding.prepare("value"), is(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2013 the original author or authors.
|
||||
* Copyright 2008-2014 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.
|
||||
@@ -22,7 +22,6 @@ import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@@ -223,18 +222,18 @@ public class ParameterBinderUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-415
|
||||
* @see DATAJPA-461
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void shouldAllowBindingOfVarArgs() throws Exception {
|
||||
public void shouldAllowBindingOfVarArgsAsIs() throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod("validWithVarArgs", Integer[].class);
|
||||
JpaParameters parameters = new JpaParameters(method);
|
||||
Integer[] ids = new Integer[] { 1, 2, 3 };
|
||||
new ParameterBinder(parameters, new Object[] { ids }).bind(query);
|
||||
|
||||
verify(query).setParameter(eq(1), eq(Arrays.asList(1, 2, 3)));
|
||||
verify(query).setParameter(eq(1), eq(ids));
|
||||
}
|
||||
|
||||
public SampleEntity findByEmbeddable(SampleEmbeddable embeddable) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -21,7 +21,9 @@ 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.jpa.repository.query.StringQuery.InParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.LikeParameterBinding;
|
||||
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
|
||||
/**
|
||||
@@ -41,13 +43,13 @@ public class StringQueryUnitTests {
|
||||
String source = "select from User u where u.firstname like :firstname";
|
||||
StringQuery query = new StringQuery(source);
|
||||
|
||||
assertThat(query.hasLikeBindings(), is(true));
|
||||
assertThat(query.hasParameterBindings(), is(true));
|
||||
assertThat(query.getQueryString(), is(source));
|
||||
|
||||
List<LikeBinding> bindings = query.getLikeBindings();
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings, hasSize(1));
|
||||
|
||||
LikeBinding binding = bindings.get(0);
|
||||
LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0);
|
||||
assertThat(binding.getType(), is(Type.LIKE));
|
||||
assertThat(binding.hasName("firstname"), is(true));
|
||||
}
|
||||
@@ -57,18 +59,18 @@ public class StringQueryUnitTests {
|
||||
|
||||
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.hasParameterBindings(), is(true));
|
||||
assertThat(query.getQueryString(), is("select u from User u where u.firstname like ?1 or u.lastname like ?2"));
|
||||
|
||||
List<LikeBinding> bindings = query.getLikeBindings();
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings, hasSize(2));
|
||||
|
||||
LikeBinding binding = bindings.get(0);
|
||||
LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0);
|
||||
assertThat(binding, is(notNullValue()));
|
||||
assertThat(binding.hasPosition(1), is(true));
|
||||
assertThat(binding.getType(), is(Type.CONTAINING));
|
||||
|
||||
binding = bindings.get(1);
|
||||
binding = (LikeParameterBinding) bindings.get(1);
|
||||
assertThat(binding, is(notNullValue()));
|
||||
assertThat(binding.hasPosition(2), is(true));
|
||||
assertThat(binding.getType(), is(Type.ENDING_WITH));
|
||||
@@ -79,18 +81,92 @@ public class StringQueryUnitTests {
|
||||
|
||||
StringQuery query = new StringQuery("select u from User u where u.firstname like %:firstname");
|
||||
|
||||
assertThat(query.hasLikeBindings(), is(true));
|
||||
assertThat(query.hasParameterBindings(), is(true));
|
||||
assertThat(query.getQueryString(), is("select u from User u where u.firstname like :firstname"));
|
||||
|
||||
List<LikeBinding> bindings = query.getLikeBindings();
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings, hasSize(1));
|
||||
|
||||
LikeBinding binding = bindings.get(0);
|
||||
LikeParameterBinding binding = (LikeParameterBinding) bindings.get(0);
|
||||
assertThat(binding, is(notNullValue()));
|
||||
assertThat(binding.hasName("firstname"), is(true));
|
||||
assertThat(binding.getType(), is(Type.ENDING_WITH));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-461
|
||||
*/
|
||||
@Test
|
||||
public void detectsNamedInParameterBindings() {
|
||||
|
||||
String queryString = "select u from User u where u.id in :ids";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
|
||||
assertThat(query.hasParameterBindings(), is(true));
|
||||
assertThat(query.getQueryString(), is(queryString));
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings, hasSize(1));
|
||||
|
||||
assertNamedBinding(InParameterBinding.class, "ids", bindings.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-461
|
||||
*/
|
||||
@Test
|
||||
public void detectsMultipleNamedInParameterBindings() {
|
||||
|
||||
String queryString = "select u from User u where u.id in :ids and u.name in :names and foo = :bar";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
|
||||
assertThat(query.hasParameterBindings(), is(true));
|
||||
assertThat(query.getQueryString(), is(queryString));
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings, hasSize(2));
|
||||
|
||||
assertNamedBinding(InParameterBinding.class, "ids", bindings.get(0));
|
||||
assertNamedBinding(InParameterBinding.class, "names", bindings.get(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-461
|
||||
*/
|
||||
@Test
|
||||
public void detectsPositionalInParameterBindings() {
|
||||
|
||||
String queryString = "select u from User u where u.id in ?1";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
|
||||
assertThat(query.hasParameterBindings(), is(true));
|
||||
assertThat(query.getQueryString(), is(queryString));
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings, hasSize(1));
|
||||
|
||||
assertPositionalBinding(InParameterBinding.class, 1, bindings.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-461
|
||||
*/
|
||||
@Test
|
||||
public void detectsMultiplePositionalInParameterBindings() {
|
||||
|
||||
String queryString = "select u from User u where u.id in ?1 and u.names in ?2 and foo = ?3";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
|
||||
assertThat(query.hasParameterBindings(), is(true));
|
||||
assertThat(query.getQueryString(), is(queryString));
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
assertThat(bindings, hasSize(2));
|
||||
|
||||
assertPositionalBinding(InParameterBinding.class, 1, bindings.get(0));
|
||||
assertPositionalBinding(InParameterBinding.class, 2, bindings.get(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-373
|
||||
*/
|
||||
@@ -103,4 +179,20 @@ public class StringQueryUnitTests {
|
||||
public void rejectsDifferentBindingsForRepeatedParameter() {
|
||||
new StringQuery("select u from User u where u.firstname like %?1 and u.lastname like ?1%");
|
||||
}
|
||||
|
||||
private void assertPositionalBinding(Class<? extends ParameterBinding> bindingType, Integer position,
|
||||
ParameterBinding expectedBinding) {
|
||||
|
||||
assertThat(bindingType.isInstance(expectedBinding), is(true));
|
||||
assertThat(expectedBinding, is(notNullValue()));
|
||||
assertThat(expectedBinding.hasPosition(position), is(true));
|
||||
}
|
||||
|
||||
private void assertNamedBinding(Class<? extends ParameterBinding> bindingType, String parameterName,
|
||||
ParameterBinding expectedBinding) {
|
||||
|
||||
assertThat(bindingType.isInstance(expectedBinding), is(true));
|
||||
assertThat(expectedBinding, is(notNullValue()));
|
||||
assertThat(expectedBinding.hasName(parameterName), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2013 the original author or authors.
|
||||
* Copyright 2008-2014 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.
|
||||
@@ -277,6 +277,18 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
|
||||
*/
|
||||
Collection<User> findByIdIn(@Param("ids") Integer... ids);
|
||||
|
||||
/**
|
||||
* @see DATAJPA-461
|
||||
*/
|
||||
@Query("select u from User u where u.id in ?1")
|
||||
Collection<User> findByIdsCustomWithPositionalVarArgs(Integer... ids);
|
||||
|
||||
/**
|
||||
* @see DATAJPA-461
|
||||
*/
|
||||
@Query("select u from User u where u.id in :ids")
|
||||
Collection<User> findByIdsCustomWithNamedVarArgs(@Param("ids") Integer... ids);
|
||||
|
||||
/**
|
||||
* @see DATAJPA-415
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user