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
This commit is contained in:
Christoph Strobl
2024-11-20 10:07:56 +01:00
parent 186d9f550f
commit f8b74eda5b
6 changed files with 51 additions and 17 deletions

View File

@@ -307,15 +307,18 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
Expression<Boolean> falsePath = getTypedPath(root, part);
return builder.isFalse(falsePath);
case SIMPLE_PROPERTY:
case NEGATING_SIMPLE_PROPERTY:
ParameterMetadata<Object> expression = provider.next(part);
Expression<Object> path = getTypedPath(root, part);
return expression.isIsNullParameter() ? path.isNull()
: builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()));
case NEGATING_SIMPLE_PROPERTY:
ParameterMetadata<Object> negatedExpression = provider.next(part);
Expression<Object> negatedPath = getTypedPath(root, part);
return negatedExpression.isIsNullParameter() ? negatedPath.isNotNull()
: builder.notEqual(upperIfIgnoreCase(negatedPath), upperIfIgnoreCase(negatedExpression.getExpression()));
if (expression.isIsNullParameter()) {
return type.equals(SIMPLE_PROPERTY) ? path.isNull() : path.isNotNull();
} else {
return type.equals(SIMPLE_PROPERTY)
? builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()))
: builder.notEqual(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()));
}
case IS_EMPTY:
case IS_NOT_EMPTY:

View File

@@ -208,7 +208,10 @@ class ParameterMetadataProvider {
EscapeCharacter escape) {
this.expression = expression;
this.type = value == null && Type.SIMPLE_PROPERTY.equals(part.getType()) ? Type.IS_NULL : part.getType();
this.type = value == null
&& (Type.SIMPLE_PROPERTY.equals(part.getType()) || Type.NEGATING_SIMPLE_PROPERTY.equals(part.getType()))
? Type.IS_NULL
: part.getType();
this.ignoreCase = IgnoreCaseType.ALWAYS.equals(part.shouldIgnoreCase());
this.noWildcards = part.getProperty().getLeafProperty().isCollection();
this.escape = escape;

View File

@@ -58,6 +58,7 @@ import org.springframework.transaction.annotation.Transactional;
* @author Krzysztof Krason
* @author Greg Turnquist
* @author Mark Paluch
* @author Christoph Strobl
* @see QueryLookupStrategy
*/
@ExtendWith(SpringExtension.class)
@@ -386,4 +387,22 @@ class UserRepositoryFinderTests {
assertThat(dtos).flatExtracting(UserRepository.NameOnly::getLastname) //
.containsExactly("Matthews", "Beauford", "Matthews");
}
@Test // GH-3675
void findBySimplePropertyUsingMixedNullNonNullArgument() {
List<User> result = userRepository.findUserByLastname(null);
assertThat(result).isEmpty();
result = userRepository.findUserByLastname(carter.getLastname());
assertThat(result).containsExactly(carter);
}
@Test // GH-3675
void findByNegatingSimplePropertyUsingMixedNullNonNullArgument() {
List<User> result = userRepository.findByLastnameNot(null);
assertThat(result).isNotEmpty();
result = userRepository.findUserByLastname(carter.getLastname());
assertThat(result).containsExactly(carter);
}
}

View File

@@ -37,6 +37,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -59,6 +61,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
* @author Michael Cramer
* @author Jens Schauder
* @author Krzysztof Krason
* @author Christoph Strobl
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:infrastructure.xml")
@@ -100,20 +103,21 @@ class PartTreeJpaQueryIntegrationTests {
testIgnoreCase("findByIdAllIgnoringCase", 3);
}
@Test // DATAJPA-121
@Disabled // HHH-15432
void recreatesQueryIfNullValueIsGiven() throws Exception {
@ParameterizedTest // DATAJPA-121, GH-3675
@ValueSource(strings = { "Firstname", "FirstnameNot" })
void recreatesQueryIfNullValueIsGiven(String criteria) throws Exception {
JpaQueryMethod queryMethod = getQueryMethod("findByFirstname", String.class, Pageable.class);
JpaQueryMethod queryMethod = getQueryMethod("findBy%s".formatted(criteria), String.class, Pageable.class);
PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager);
Query query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { "Matthews", PageRequest.of(0, 1) }));
assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).endsWith("firstname=:param0");
assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY)))
.contains("firstname %s :".formatted(criteria.endsWith("Not") ? "<>" : "="));
query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { null, PageRequest.of(0, 1) }));
assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).endsWith("firstname is null");
assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY)))
.endsWithIgnoringCase("firstname %s NULL".formatted(criteria.endsWith("Not") ? "IS NOT" : "IS"));
}
@Test // DATAJPA-920
@@ -277,6 +281,8 @@ class PartTreeJpaQueryIntegrationTests {
Page<User> findByFirstname(String firstname, Pageable pageable);
Page<User> findByFirstnameNot(String firstname, Pageable pageable);
User findByIdIgnoringCase(Integer id);
User findByIdAllIgnoringCase(Integer id);

View File

@@ -48,6 +48,7 @@ import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.data.querydsl.ListQuerydslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -75,6 +76,8 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
@QueryHints({ @QueryHint(name = "foo", value = "bar") })
List<User> findByLastname(String lastname);
List<User> findUserByLastname(@Nullable String lastname);
/**
* Redeclaration of {@link CrudRepository#findById(java.lang.Object)} to change transaction configuration.
*/
@@ -177,7 +180,7 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
List<User> findByLastnameNotLike(String lastname);
List<User> findByLastnameNot(String lastname);
List<User> findByLastnameNot(@Nullable String lastname);
List<User> findByManagerLastname(String name);

View File

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