Skip to content

Commit 3f71db4

Browse files
schaudermp911de
authored andcommitted
DATAJPA-1182 - Validate method parameters on query construction.
For “In” and “NotIn” queries not providing a collection like argument results in an IllegalStateException at initialisation time. Conversely providing a collection as argument for a query other than “In” or “NotIn” also throws such an exception. Original pull request: #228.
1 parent 8b10b6c commit 3f71db4

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java

+74
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18+
import java.util.Collection;
1819
import java.util.List;
1920
import java.util.Optional;
2021

@@ -26,13 +27,17 @@
2627

2728
import org.springframework.data.domain.Sort;
2829
import org.springframework.data.jpa.provider.PersistenceProvider;
30+
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
2931
import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution;
3032
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ExistsExecution;
3133
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
3234
import org.springframework.data.repository.query.ParametersParameterAccessor;
3335
import org.springframework.data.repository.query.ResultProcessor;
3436
import org.springframework.data.repository.query.ReturnedType;
37+
import org.springframework.data.repository.query.parser.Part;
38+
import org.springframework.data.repository.query.parser.Part.Type;
3539
import org.springframework.data.repository.query.parser.PartTree;
40+
import org.springframework.data.repository.query.parser.PartTree.OrPart;
3641
import org.springframework.lang.Nullable;
3742

3843
/**
@@ -76,6 +81,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
7681
try {
7782

7883
this.tree = new PartTree(method.getName(), domainClass);
84+
validate(tree, parameters, method.toString());
7985
this.countQuery = new CountQueryPreparer(persistenceProvider, recreationRequired);
8086
this.query = tree.isCountProjection() ? countQuery : new QueryPreparer(persistenceProvider, recreationRequired);
8187

@@ -120,6 +126,74 @@ protected JpaQueryExecution getExecution() {
120126
return super.getExecution();
121127
}
122128

129+
private static void validate(PartTree tree, JpaParameters parameters, String methodName) {
130+
131+
int argCount = 0;
132+
133+
for (OrPart orPart : tree) {
134+
135+
for (Part part : orPart) {
136+
137+
int numberOfArguments = part.getNumberOfArguments();
138+
139+
for (int i = 0; i < numberOfArguments; i++) {
140+
141+
throwExceptionOnArgumentMismatch(methodName, part, parameters, argCount);
142+
143+
argCount++;
144+
}
145+
}
146+
}
147+
}
148+
149+
private static void throwExceptionOnArgumentMismatch(String methodName, Part part, JpaParameters parameters,
150+
int index) {
151+
152+
Type type = part.getType();
153+
String property = part.getProperty().toDotPath();
154+
155+
if (!parameters.getBindableParameters().hasParameterAt(index)) {
156+
throw new IllegalStateException(String.format(
157+
"For the method %s we expect at least %d arguments but only found %d. This leaves an operator of type %s for property %s unbound.",
158+
methodName, index + 1, index, type.name(), property));
159+
}
160+
161+
JpaParameter parameter = parameters.getBindableParameter(index);
162+
163+
if (expectsCollection(type) && !parameterIsCollectionLike(parameter)) {
164+
throw new IllegalStateException(wrongParameterTypeMessage(methodName, property, type, "Collection", parameter));
165+
} else if (!expectsCollection(type) && !parameterIsScalarLike(parameter)) {
166+
throw new IllegalStateException(wrongParameterTypeMessage(methodName, property, type, "scalar", parameter));
167+
}
168+
}
169+
170+
private static String wrongParameterTypeMessage(String methodName, String property, Type operatorType,
171+
String expectedArgumenType, JpaParameter parameter) {
172+
173+
return String.format( //
174+
"The operator %s on %s requires a %s argument, but we found %s in method %s", //
175+
operatorType.name(), //
176+
property, expectedArgumenType, //
177+
parameter.getType(), //
178+
methodName //
179+
);
180+
}
181+
182+
private static boolean parameterIsCollectionLike(JpaParameter parameter) {
183+
return Collection.class.isAssignableFrom(parameter.getType()) || parameter.getType().isArray();
184+
}
185+
186+
/**
187+
* Arrays are may be treated as collection like or in the case of binary data as scalar
188+
*/
189+
private static boolean parameterIsScalarLike(JpaParameter parameter) {
190+
return !Collection.class.isAssignableFrom(parameter.getType());
191+
}
192+
193+
private static boolean expectsCollection(Type type) {
194+
return type == Type.IN || type == Type.NOT_IN;
195+
}
196+
123197
/**
124198
* Query preparer to create {@link CriteriaQuery} instances and potentially cache them.
125199
*

src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java

+36-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import java.lang.reflect.Method;
2424
import java.util.Arrays;
25+
import java.util.Collection;
2526
import java.util.Date;
2627
import java.util.Iterator;
2728
import java.util.List;
@@ -31,6 +32,7 @@
3132
import javax.persistence.Query;
3233
import javax.persistence.TemporalType;
3334

35+
import org.assertj.core.api.Assertions;
3436
import org.hibernate.Version;
3537
import org.junit.Before;
3638
import org.junit.Rule;
@@ -169,8 +171,34 @@ public void rejectsIsEmptyOnNonCollectionProperty() throws Exception {
169171
jpaQuery.createQuery(new Object[] { "Oliver" });
170172
}
171173

174+
@Test // DATAJPA-1182
175+
public void rejectsInPredicateWithNonIterableParameter() throws Exception {
176+
177+
JpaQueryMethod method = getQueryMethod("findByIdIn", Long.class);
178+
179+
Assertions.assertThatExceptionOfType(RuntimeException.class) //
180+
.isThrownBy(() -> new PartTreeJpaQuery(method, entityManager, provider)) //
181+
.withMessageContaining("findByIdIn") //
182+
.withMessageContaining(" IN ") //
183+
.withMessageContaining("Collection") //
184+
.withMessageContaining("Long");
185+
}
186+
187+
@Test // DATAJPA-1182
188+
public void rejectsOtherThanInPredicateWithIterableParameter() throws Exception {
189+
190+
JpaQueryMethod method = getQueryMethod("findById", Collection.class);
191+
192+
Assertions.assertThatExceptionOfType(RuntimeException.class) //
193+
.isThrownBy(() -> new PartTreeJpaQuery(method, entityManager, provider)) //
194+
.withMessageContaining("findById") //
195+
.withMessageContaining(" SIMPLE_PROPERTY ") //
196+
.withMessageContaining(" scalar ") //
197+
.withMessageContaining("Collection");
198+
}
199+
172200
@Test // DATAJPA-863
173-
public void errorsDueToMismatchOfParametersContainNameOfMethodAndInterface() throws Exception {
201+
public void errorsDueToMismatchOfParametersContainNameOfMethodInterfaceAndPropertyPath() throws Exception {
174202

175203
JpaQueryMethod method = getQueryMethod("findByFirstname");
176204

@@ -208,6 +236,7 @@ private void testIgnoreCase(String methodName, Object... values) throws Exceptio
208236
}
209237

210238
private JpaQueryMethod getQueryMethod(String methodName, Class<?>... parameterTypes) throws Exception {
239+
211240
Method method = UserRepository.class.getMethod(methodName, parameterTypes);
212241
return new JpaQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
213242
new SpelAwareProxyProjectionFactory(), PersistenceProvider.fromEntityManager(entityManager));
@@ -260,6 +289,12 @@ interface UserRepository extends Repository<User, Long> {
260289

261290
List<User> findByFirstnameIsEmpty();
262291

292+
// should fail, since we can't compare scalar values to collections
293+
List<User> findById(Collection<Long> ids);
294+
295+
// should fail, since we can't do an IN on a scalar
296+
List<User> findByIdIn(Long id);
297+
263298
// Wrong number of parameters
264299
User findByFirstname();
265300

0 commit comments

Comments
 (0)