Skip to content

Commit 5c95016

Browse files
committed
DATAJPA-121 - Corrected query handling if null parameters are provided.
If a query is executed and null values are provided we now recreate a query with an IS NULL predicate correctly.
1 parent 9ae8caa commit 5c95016

File tree

8 files changed

+265
-150
lines changed

8 files changed

+265
-150
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
<spring.version.30>3.0.6.RELEASE</spring.version.30>
5656
<spring.version.40>4.0.0.RELEASE</spring.version.40>
5757
<spring.version.range>[${spring.version.30}, ${spring.version.40})</spring.version.range>
58-
<spring.data.commons.version>1.2.0.BUILD-SNAPSHOT</spring.data.commons.version>
58+
<spring.data.commons.version>1.3.0.BUILD-SNAPSHOT</spring.data.commons.version>
5959
<hibernate.version>3.6.9.Final</hibernate.version>
6060
<openjpa.version>2.1.1</openjpa.version>
6161
<eclipselink.version>2.3.2</eclipselink.version>

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.persistence.Query;
2525
import javax.persistence.criteria.ParameterExpression;
2626

27+
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
2728
import org.springframework.data.repository.query.Parameter;
2829
import org.springframework.data.repository.query.Parameters;
2930
import org.springframework.util.Assert;
@@ -36,15 +37,15 @@
3637
*/
3738
class CriteriaQueryParameterBinder extends ParameterBinder {
3839

39-
private final Iterator<ParameterExpression<?>> expressions;
40+
private final Iterator<ParameterMetadata<?>> expressions;
4041

4142
/**
4243
* Creates a new {@link CriteriaQueryParameterBinder} for the given {@link Parameters}, values and some
4344
* {@link ParameterExpression}.
4445
*
4546
* @param parameters
4647
*/
47-
CriteriaQueryParameterBinder(Parameters parameters, Object[] values, Iterable<ParameterExpression<?>> expressions) {
48+
CriteriaQueryParameterBinder(Parameters parameters, Object[] values, Iterable<ParameterMetadata<?>> expressions) {
4849

4950
super(parameters, values);
5051
Assert.notNull(expressions);
@@ -63,10 +64,15 @@ class CriteriaQueryParameterBinder extends ParameterBinder {
6364
@SuppressWarnings("unchecked")
6465
protected void bind(Query query, Parameter parameter, Object value, int position) {
6566

66-
ParameterExpression<Object> expression = (ParameterExpression<Object>) expressions.next();
67+
ParameterMetadata<Object> parameterMetadata = (ParameterMetadata<Object>) expressions.next();
6768

68-
Object valueToBind = Collection.class.equals(expression.getJavaType()) ? toCollection(value) : value;
69+
if (parameterMetadata.isIsNullParameter()) {
70+
return;
71+
}
6972

73+
ParameterExpression<Object> expression = parameterMetadata.getExpression();
74+
75+
Object valueToBind = Collection.class.equals(expression.getJavaType()) ? toCollection(value) : value;
7076
query.setParameter(expression, valueToBind);
7177
}
7278

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import javax.persistence.EntityManager;
1918
import javax.persistence.criteria.CriteriaBuilder;
2019
import javax.persistence.criteria.CriteriaQuery;
2120
import javax.persistence.criteria.Predicate;
2221
import javax.persistence.criteria.Root;
2322

2423
import org.springframework.data.domain.Sort;
25-
import org.springframework.data.repository.query.Parameters;
2624
import org.springframework.data.repository.query.parser.PartTree;
2725

2826
/**
@@ -40,9 +38,10 @@ public class JpaCountQueryCreator extends JpaQueryCreator {
4038
* @param parameters
4139
* @param em
4240
*/
43-
public JpaCountQueryCreator(PartTree tree, Class<?> domainClass, Parameters parameters, EntityManager em) {
41+
public JpaCountQueryCreator(PartTree tree, Class<?> domainClass, CriteriaBuilder builder,
42+
ParameterMetadataProvider provider) {
4443

45-
super(tree, domainClass, parameters, em);
44+
super(tree, domainClass, builder, provider);
4645
}
4746

4847
/*

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

Lines changed: 27 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,10 @@
1717

1818
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
1919

20-
import java.util.ArrayList;
2120
import java.util.Collection;
22-
import java.util.Collections;
2321
import java.util.Iterator;
2422
import java.util.List;
2523

26-
import javax.persistence.EntityManager;
2724
import javax.persistence.criteria.CriteriaBuilder;
2825
import javax.persistence.criteria.CriteriaQuery;
2926
import javax.persistence.criteria.Expression;
@@ -32,15 +29,13 @@
3229
import javax.persistence.criteria.Root;
3330

3431
import org.springframework.data.domain.Sort;
32+
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
3533
import org.springframework.data.mapping.PropertyPath;
36-
import org.springframework.data.repository.query.Parameter;
37-
import org.springframework.data.repository.query.Parameters;
3834
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
3935
import org.springframework.data.repository.query.parser.Part;
4036
import org.springframework.data.repository.query.parser.Part.Type;
4137
import org.springframework.data.repository.query.parser.PartTree;
4238
import org.springframework.util.Assert;
43-
import org.springframework.util.ClassUtils;
4439

4540
/**
4641
* Query creator to create a {@link CriteriaQuery} from a {@link PartTree}.
@@ -52,7 +47,7 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<Object>,
5247
private final CriteriaBuilder builder;
5348
private final Root<?> root;
5449
private final CriteriaQuery<Object> query;
55-
private final ParameterExpressionProvider provider;
50+
private final ParameterMetadataProvider provider;
5651

5752
/**
5853
* Create a new {@link JpaQueryCreator}.
@@ -62,33 +57,29 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<Object>,
6257
* @param accessor
6358
* @param em
6459
*/
65-
public JpaQueryCreator(PartTree tree, Class<?> domainClass, Parameters parameters, EntityManager em) {
60+
public JpaQueryCreator(PartTree tree, Class<?> domainClass, CriteriaBuilder builder,
61+
ParameterMetadataProvider provider) {
6662

6763
super(tree);
6864

69-
this.builder = em.getCriteriaBuilder();
65+
this.builder = builder;
7066
this.query = builder.createQuery().distinct(tree.isDistinct());
7167
this.root = query.from(domainClass);
72-
this.provider = new ParameterExpressionProvider(builder, parameters.getBindableParameters());
68+
this.provider = provider;
7369
}
7470

7571
/**
7672
* Returns all {@link ParameterExpression} created when creating the query.
7773
*
7874
* @return the parameterExpressions
7975
*/
80-
public List<ParameterExpression<?>> getParameterExpressions() {
81-
76+
public List<ParameterMetadata<?>> getParameterExpressions() {
8277
return provider.getExpressions();
8378
}
8479

8580
/*
8681
* (non-Javadoc)
87-
*
88-
* @see
89-
* org.springframework.data.repository.query.parser.AbstractQueryCreator
90-
* #create(org.springframework.data.repository.query.parser.Part,
91-
* java.util.Iterator)
82+
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator)
9283
*/
9384
@Override
9485
protected Predicate create(Part part, Iterator<Object> iterator) {
@@ -98,11 +89,7 @@ protected Predicate create(Part part, Iterator<Object> iterator) {
9889

9990
/*
10091
* (non-Javadoc)
101-
*
102-
* @see
103-
* org.springframework.data.repository.query.parser.AbstractQueryCreator
104-
* #and(org.springframework.data.repository.query.parser.Part,
105-
* java.lang.Object, java.util.Iterator)
92+
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator)
10693
*/
10794
@Override
10895
protected Predicate and(Part part, Predicate base, Iterator<Object> iterator) {
@@ -112,10 +99,7 @@ protected Predicate and(Part part, Predicate base, Iterator<Object> iterator) {
11299

113100
/*
114101
* (non-Javadoc)
115-
*
116-
* @see
117-
* org.springframework.data.repository.query.parser.AbstractQueryCreator
118-
* #or(java.lang.Object, java.lang.Object)
102+
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object)
119103
*/
120104
@Override
121105
protected Predicate or(Predicate base, Predicate predicate) {
@@ -146,7 +130,6 @@ protected final CriteriaQuery<Object> complete(Predicate predicate, Sort sort) {
146130
*/
147131
protected CriteriaQuery<Object> complete(Predicate predicate, Sort sort, CriteriaQuery<Object> query,
148132
CriteriaBuilder builder, Root<?> root) {
149-
150133
return this.query.select(root).where(predicate).orderBy(QueryUtils.toOrders(sort, root, builder));
151134
}
152135

@@ -159,7 +142,6 @@ protected CriteriaQuery<Object> complete(Predicate predicate, Sort sort, Criteri
159142
* @return
160143
*/
161144
private Predicate toPredicate(Part part, Root<?> root) {
162-
163145
return new PredicateBuilder(part, root).build();
164146
}
165147

@@ -180,89 +162,6 @@ private <T> Expression<T> getTypedPath(Root<?> root, Part part, Class<T> type) {
180162
return toExpressionRecursively(root, part.getProperty());
181163
}
182164

183-
/**
184-
* Helper class to allow easy creation of {@link ParameterExpression}s.
185-
*
186-
* @author Oliver Gierke
187-
*/
188-
static class ParameterExpressionProvider {
189-
190-
private final CriteriaBuilder builder;
191-
private final Iterator<Parameter> parameters;
192-
private final List<ParameterExpression<?>> expressions;
193-
194-
/**
195-
* Creates a new {@link ParameterExpressionProvider} from the given {@link CriteriaBuilder} and {@link Parameters}.
196-
*
197-
* @param builder
198-
* @param parameters
199-
*/
200-
public ParameterExpressionProvider(CriteriaBuilder builder, Parameters parameters) {
201-
202-
Assert.notNull(builder);
203-
Assert.notNull(parameters);
204-
205-
this.builder = builder;
206-
this.parameters = parameters.iterator();
207-
this.expressions = new ArrayList<ParameterExpression<?>>();
208-
}
209-
210-
/**
211-
* Returns all {@link ParameterExpression}s built.
212-
*
213-
* @return the expressions
214-
*/
215-
public List<ParameterExpression<?>> getExpressions() {
216-
217-
return Collections.unmodifiableList(expressions);
218-
}
219-
220-
/**
221-
* Builds a new {@link ParameterExpression} for the next {@link Parameter}.
222-
*
223-
* @param <T>
224-
* @return
225-
*/
226-
@SuppressWarnings("unchecked")
227-
public <T> ParameterExpression<T> next() {
228-
229-
Parameter parameter = parameters.next();
230-
return (ParameterExpression<T>) next(parameter.getType(), parameter.getName());
231-
}
232-
233-
/**
234-
* Builds a new {@link ParameterExpression} of the given type. Forwards the underlying {@link Parameters} as well.
235-
*
236-
* @param <T>
237-
* @param type must not be {@literal null}.
238-
* @return
239-
*/
240-
@SuppressWarnings("unchecked")
241-
public <T> ParameterExpression<? extends T> next(Class<T> type) {
242-
243-
Parameter parameter = parameters.next();
244-
Class<?> typeToUse = ClassUtils.isAssignable(type, parameter.getType()) ? parameter.getType() : type;
245-
return (ParameterExpression<? extends T>) next(typeToUse, null);
246-
}
247-
248-
/**
249-
* Builds a new {@link ParameterExpression} for the given type and name.
250-
*
251-
* @param <T>
252-
* @param type must not be {@literal null}.
253-
* @param name
254-
* @return
255-
*/
256-
private <T> ParameterExpression<T> next(Class<T> type, String name) {
257-
258-
Assert.notNull(type);
259-
260-
ParameterExpression<T> expression = name == null ? builder.parameter(type) : builder.parameter(type, name);
261-
expressions.add(expression);
262-
return expression;
263-
}
264-
}
265-
266165
/**
267166
* Simple builder to contain logic to create JPA {@link Predicate}s from {@link Part}s.
268167
*
@@ -300,39 +199,44 @@ public Predicate build() {
300199

301200
switch (part.getType()) {
302201
case BETWEEN:
303-
ParameterExpression<Comparable> first = provider.next();
304-
ParameterExpression<Comparable> second = provider.next();
305-
return builder.between(getComparablePath(root, part), first, second);
202+
ParameterMetadata<Comparable> first = provider.next(part);
203+
ParameterMetadata<Comparable> second = provider.next(part);
204+
return builder.between(getComparablePath(root, part), first.getExpression(), second.getExpression());
306205
case GREATER_THAN:
307-
return builder.greaterThan(getComparablePath(root, part), provider.next(Comparable.class));
206+
return builder
207+
.greaterThan(getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
308208
case GREATER_THAN_EQUAL:
309-
return builder.greaterThanOrEqualTo(getComparablePath(root, part), provider.next(Comparable.class));
209+
return builder.greaterThanOrEqualTo(getComparablePath(root, part), provider.next(part, Comparable.class)
210+
.getExpression());
310211
case LESS_THAN:
311-
return builder.lessThan(getComparablePath(root, part), provider.next(Comparable.class));
212+
return builder.lessThan(getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
312213
case LESS_THAN_EQUAL:
313-
return builder.lessThanOrEqualTo(getComparablePath(root, part), provider.next(Comparable.class));
214+
return builder.lessThanOrEqualTo(getComparablePath(root, part), provider.next(part, Comparable.class)
215+
.getExpression());
314216
case IS_NULL:
315217
return path.isNull();
316218
case IS_NOT_NULL:
317219
return path.isNotNull();
318220
case NOT_IN:
319-
return path.in(provider.next(Collection.class)).not();
221+
return path.in(provider.next(part, Collection.class).getExpression()).not();
320222
case IN:
321-
return path.in(provider.next(Collection.class));
223+
return path.in(provider.next(part, Collection.class).getExpression());
322224
case LIKE:
323225
case NOT_LIKE:
324226
Expression<String> propertyExpression = upperIfIgnoreCase(getTypedPath(root, part, String.class));
325-
Expression<String> parameterExpression = upperIfIgnoreCase(provider.next(String.class));
227+
Expression<String> parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression());
326228
Predicate like = builder.like(propertyExpression, parameterExpression);
327229
return part.getType() == Type.LIKE ? like : like.not();
328230
case TRUE:
329231
return builder.isTrue(getTypedPath(root, part, Boolean.class));
330232
case FALSE:
331233
return builder.isFalse(getTypedPath(root, part, Boolean.class));
332234
case SIMPLE_PROPERTY:
333-
return builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(provider.next()));
235+
ParameterMetadata<Object> expression = provider.next(part);
236+
return expression.isIsNullParameter() ? path.isNull() : builder.equal(upperIfIgnoreCase(path),
237+
upperIfIgnoreCase(expression.getExpression()));
334238
case NEGATING_SIMPLE_PROPERTY:
335-
return builder.notEqual(upperIfIgnoreCase(path), upperIfIgnoreCase(provider.next()));
239+
return builder.notEqual(upperIfIgnoreCase(path), upperIfIgnoreCase(provider.next(part).getExpression()));
336240
default:
337241
throw new IllegalArgumentException("Unsupported keyword + " + part.getType());
338242
}

0 commit comments

Comments
 (0)