Skip to content

Commit f8b74ed

Browse files
Polishing.
Update documentation. Make sure the ParameterMetadataProivder recognises negating properties with null values as IS_NULL type. Reenabled query recreation test and add missing finder integration tests. Original Pull Request: #3681
1 parent 186d9f5 commit f8b74ed

File tree

6 files changed

+51
-17
lines changed

6 files changed

+51
-17
lines changed

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -307,15 +307,18 @@ public Predicate build() {
307307
Expression<Boolean> falsePath = getTypedPath(root, part);
308308
return builder.isFalse(falsePath);
309309
case SIMPLE_PROPERTY:
310+
case NEGATING_SIMPLE_PROPERTY:
311+
310312
ParameterMetadata<Object> expression = provider.next(part);
311313
Expression<Object> path = getTypedPath(root, part);
312-
return expression.isIsNullParameter() ? path.isNull()
313-
: builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()));
314-
case NEGATING_SIMPLE_PROPERTY:
315-
ParameterMetadata<Object> negatedExpression = provider.next(part);
316-
Expression<Object> negatedPath = getTypedPath(root, part);
317-
return negatedExpression.isIsNullParameter() ? negatedPath.isNotNull()
318-
: builder.notEqual(upperIfIgnoreCase(negatedPath), upperIfIgnoreCase(negatedExpression.getExpression()));
314+
315+
if (expression.isIsNullParameter()) {
316+
return type.equals(SIMPLE_PROPERTY) ? path.isNull() : path.isNotNull();
317+
} else {
318+
return type.equals(SIMPLE_PROPERTY)
319+
? builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()))
320+
: builder.notEqual(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()));
321+
}
319322
case IS_EMPTY:
320323
case IS_NOT_EMPTY:
321324

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,10 @@ public ParameterMetadata(ParameterExpression<T> expression, Part part, @Nullable
208208
EscapeCharacter escape) {
209209

210210
this.expression = expression;
211-
this.type = value == null && Type.SIMPLE_PROPERTY.equals(part.getType()) ? Type.IS_NULL : part.getType();
211+
this.type = value == null
212+
&& (Type.SIMPLE_PROPERTY.equals(part.getType()) || Type.NEGATING_SIMPLE_PROPERTY.equals(part.getType()))
213+
? Type.IS_NULL
214+
: part.getType();
212215
this.ignoreCase = IgnoreCaseType.ALWAYS.equals(part.shouldIgnoreCase());
213216
this.noWildcards = part.getProperty().getLeafProperty().isCollection();
214217
this.escape = escape;

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
* @author Krzysztof Krason
5959
* @author Greg Turnquist
6060
* @author Mark Paluch
61+
* @author Christoph Strobl
6162
* @see QueryLookupStrategy
6263
*/
6364
@ExtendWith(SpringExtension.class)
@@ -386,4 +387,22 @@ public void selectProjectionWithSubselect() {
386387
assertThat(dtos).flatExtracting(UserRepository.NameOnly::getLastname) //
387388
.containsExactly("Matthews", "Beauford", "Matthews");
388389
}
390+
391+
@Test // GH-3675
392+
void findBySimplePropertyUsingMixedNullNonNullArgument() {
393+
394+
List<User> result = userRepository.findUserByLastname(null);
395+
assertThat(result).isEmpty();
396+
result = userRepository.findUserByLastname(carter.getLastname());
397+
assertThat(result).containsExactly(carter);
398+
}
399+
400+
@Test // GH-3675
401+
void findByNegatingSimplePropertyUsingMixedNullNonNullArgument() {
402+
403+
List<User> result = userRepository.findByLastnameNot(null);
404+
assertThat(result).isNotEmpty();
405+
result = userRepository.findUserByLastname(carter.getLastname());
406+
assertThat(result).containsExactly(carter);
407+
}
389408
}

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import org.junit.jupiter.api.Disabled;
3838
import org.junit.jupiter.api.Test;
3939
import org.junit.jupiter.api.extension.ExtendWith;
40+
import org.junit.jupiter.params.ParameterizedTest;
41+
import org.junit.jupiter.params.provider.ValueSource;
4042
import org.springframework.data.domain.Page;
4143
import org.springframework.data.domain.PageRequest;
4244
import org.springframework.data.domain.Pageable;
@@ -59,6 +61,7 @@
5961
* @author Michael Cramer
6062
* @author Jens Schauder
6163
* @author Krzysztof Krason
64+
* @author Christoph Strobl
6265
*/
6366
@ExtendWith(SpringExtension.class)
6467
@ContextConfiguration("classpath:infrastructure.xml")
@@ -100,20 +103,21 @@ void cannotIgnoreCaseIfNotStringUnlessIgnoringAll() throws Exception {
100103
testIgnoreCase("findByIdAllIgnoringCase", 3);
101104
}
102105

103-
@Test // DATAJPA-121
104-
@Disabled // HHH-15432
105-
void recreatesQueryIfNullValueIsGiven() throws Exception {
106+
@ParameterizedTest // DATAJPA-121, GH-3675
107+
@ValueSource(strings = { "Firstname", "FirstnameNot" })
108+
void recreatesQueryIfNullValueIsGiven(String criteria) throws Exception {
106109

107-
JpaQueryMethod queryMethod = getQueryMethod("findByFirstname", String.class, Pageable.class);
110+
JpaQueryMethod queryMethod = getQueryMethod("findBy%s".formatted(criteria), String.class, Pageable.class);
108111
PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager);
109112

110113
Query query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { "Matthews", PageRequest.of(0, 1) }));
111-
112-
assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).endsWith("firstname=:param0");
114+
assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY)))
115+
.contains("firstname %s :".formatted(criteria.endsWith("Not") ? "<>" : "="));
113116

114117
query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { null, PageRequest.of(0, 1) }));
115118

116-
assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).endsWith("firstname is null");
119+
assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY)))
120+
.endsWithIgnoringCase("firstname %s NULL".formatted(criteria.endsWith("Not") ? "IS NOT" : "IS"));
117121
}
118122

119123
@Test // DATAJPA-920
@@ -277,6 +281,8 @@ interface UserRepository extends Repository<User, Integer> {
277281

278282
Page<User> findByFirstname(String firstname, Pageable pageable);
279283

284+
Page<User> findByFirstnameNot(String firstname, Pageable pageable);
285+
280286
User findByIdIgnoringCase(Integer id);
281287

282288
User findByIdAllIgnoringCase(Integer id);

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.springframework.data.querydsl.ListQuerydslPredicateExecutor;
4949
import org.springframework.data.repository.CrudRepository;
5050
import org.springframework.data.repository.query.Param;
51+
import org.springframework.lang.Nullable;
5152
import org.springframework.transaction.annotation.Transactional;
5253

5354
/**
@@ -75,6 +76,8 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
7576
@QueryHints({ @QueryHint(name = "foo", value = "bar") })
7677
List<User> findByLastname(String lastname);
7778

79+
List<User> findUserByLastname(@Nullable String lastname);
80+
7881
/**
7982
* Redeclaration of {@link CrudRepository#findById(java.lang.Object)} to change transaction configuration.
8083
*/
@@ -177,7 +180,7 @@ Window<User> findTop3ByFirstnameStartingWithOrderByFirstnameAscEmailAddressAsc(S
177180

178181
List<User> findByLastnameNotLike(String lastname);
179182

180-
List<User> findByLastnameNot(String lastname);
183+
List<User> findByLastnameNot(@Nullable String lastname);
181184

182185
List<User> findByManagerLastname(String name);
183186

src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ The following table describes the keywords supported for JPA and what a method c
6060
|`EndingWith`|`findByFirstnameEndingWith`|`… where x.firstname like ?1` (parameter bound with prepended `%`)
6161
|`Containing`|`findByFirstnameContaining`|`… where x.firstname like ?1` (parameter bound wrapped in `%`)
6262
|`OrderBy`|`findByAgeOrderByLastnameDesc`|`… where x.age = ?1 order by x.lastname desc`
63-
|`Not`|`findByLastnameNot`|`… where x.lastname <> ?1`
63+
|`Not`|`findByLastnameNot`|`… where x.lastname <> ?1` (or `… where x.lastname IS NOT NULL` if the argument is `null`)
6464
|`In`|`findByAgeIn(Collection<Age> ages)`|`… where x.age in ?1`
6565
|`NotIn`|`findByAgeNotIn(Collection<Age> ages)`|`… where x.age not in ?1`
6666
|`True`|`findByActiveTrue()`|`… where x.active = true`

0 commit comments

Comments
 (0)