Skip to content

Commit 747a3ee

Browse files
committed
Ensure sequential parameter binding numbering.
We now no longer allocate a parameter binding position for parameters that are represented with IS NULL or IS NOT NULL to ensure consistent parameter binding position numbering without gaps. Closes #4171
1 parent 0b66a31 commit 747a3ee

File tree

4 files changed

+44
-8
lines changed

4 files changed

+44
-8
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,16 @@ public PartTreeParameterBinding(BindingIdentifier identifier, ParameterOrigin or
239239
this.templates = templates;
240240
this.escape = escape;
241241
this.value = value;
242-
this.type = value == null
242+
this.type = getType(part, value);
243+
this.ignoreCase = Part.IgnoreCaseType.ALWAYS.equals(part.shouldIgnoreCase());
244+
this.noWildcards = part.getProperty().getLeafProperty().isCollection();
245+
}
246+
247+
static Type getType(Part part, @Nullable Object value) {
248+
return value == null
243249
&& (Type.SIMPLE_PROPERTY.equals(part.getType()) || Type.NEGATING_SIMPLE_PROPERTY.equals(part.getType()))
244250
? Type.IS_NULL
245251
: part.getType();
246-
this.ignoreCase = Part.IgnoreCaseType.ALWAYS.equals(part.shouldIgnoreCase());
247-
this.noWildcards = part.getProperty().getLeafProperty().isCollection();
248252
}
249253

250254
/**

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,10 @@ private <T> PartTreeParameterBinding next(Part part, Class<T> type, Parameter pa
214214
Class<T> reifiedType = Expression.class.equals(type) ? (Class<T>) Object.class : type;
215215

216216
Object value = bindableParameterValues == null ? PLACEHOLDER : bindableParameterValues.next();
217+
Part.Type typeToUse = PartTreeParameterBinding.getType(part, value);
217218
int currentPosition = ++position;
218-
int currentBindMarker = ++bindMarker;
219-
220-
BindingIdentifier bindingIdentifier = parameter.getName().map(it -> BindingIdentifier.of(it, currentBindMarker))
221-
.orElseGet(() -> BindingIdentifier.of(currentBindMarker));
222219

220+
BindingIdentifier bindingIdentifier = getBindingIdentifier(typeToUse, parameter);
223221
BindingIdentifier origin = parameter.getName().map(it -> BindingIdentifier.of(it, currentPosition))
224222
.orElseGet(() -> BindingIdentifier.of(currentPosition));
225223

@@ -238,6 +236,17 @@ private <T> PartTreeParameterBinding next(Part part, Class<T> type, Parameter pa
238236
return binding;
239237
}
240238

239+
private BindingIdentifier getBindingIdentifier(Part.Type type, Parameter parameter) {
240+
241+
if (type == Part.Type.IS_NULL) {
242+
return BindingIdentifier.of("null");
243+
}
244+
245+
int currentBindMarker = ++bindMarker;
246+
return parameter.getName().map(it -> BindingIdentifier.of(it, currentBindMarker))
247+
.orElseGet(() -> BindingIdentifier.of(currentBindMarker));
248+
}
249+
241250
/**
242251
* @return the scoring function if available {@link ScoringFunction#unspecified()} by default.
243252
* @since 4.0

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryCreatorTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,18 @@ void negatingSimpleNullProperty() {
125125
.validateQuery();
126126
}
127127

128+
@Test // GH-4171
129+
void simplePropertyAndNull() {
130+
131+
queryCreator(ORDER) //
132+
.forTree(Order.class, "findByIdAndCompleted") //
133+
.withParameterTypes(Long.class, Boolean.class).withParameters(null, false) //
134+
.as(QueryCreatorTester::create) //
135+
.expectJpql("SELECT o FROM %s o WHERE o.id IS NULL AND o.completed = ?1",
136+
DefaultJpaEntityMetadata.unqualify(Order.class)) //
137+
.validateQuery();
138+
}
139+
128140
@Test // GH-3588
129141
void simpleAnd() {
130142

@@ -828,6 +840,12 @@ private JpaParametersParameterAccessor accessor(Class<?>... argumentTypes) {
828840
return StubJpaParameterParameterAccessor.accessor(argumentTypes);
829841
}
830842

843+
@Entity
844+
class SampleEntity {
845+
@Id Long id;
846+
int isDeleted;
847+
}
848+
831849
@jakarta.persistence.Entity
832850
static class Order {
833851

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StubJpaParameterParameterAccessor.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Arrays;
2222
import java.util.List;
2323

24+
import org.jspecify.annotations.Nullable;
2425
import org.mockito.Mockito;
2526

2627
import org.springframework.core.MethodParameter;
@@ -49,13 +50,17 @@ static JpaParametersParameterAccessor accessor(Class<?>... parameterTypes) {
4950

5051
static AccessorBuilder accessorFor(Class<?>... parameterTypes) {
5152
return arguments -> accessor(parameterTypes, arguments);
52-
5353
}
5454

5555
interface AccessorBuilder {
5656
JpaParametersParameterAccessor withValues(Object... arguments);
5757
}
5858

59+
@Override
60+
public @Nullable Object getBindableValue(int index) {
61+
return getValues()[index];
62+
}
63+
5964
@SuppressWarnings({ "rawtypes", "unchecked" })
6065
static JpaParametersParameterAccessor accessor(Class<?>[] parameterTypes, Object... parameters) {
6166

0 commit comments

Comments
 (0)