Skip to content

Commit 53243d7

Browse files
committed
Consider NULLS precedence using Sort for Criteria Queries.
Closes: #3587 Original Pull Request: #3695
1 parent c3efdcf commit 53243d7

File tree

2 files changed

+20
-9
lines changed

2 files changed

+20
-9
lines changed

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

+13-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import jakarta.persistence.criteria.From;
3030
import jakarta.persistence.criteria.Join;
3131
import jakarta.persistence.criteria.JoinType;
32+
import jakarta.persistence.criteria.Nulls;
3233
import jakarta.persistence.criteria.Path;
3334
import jakarta.persistence.metamodel.Attribute;
3435
import jakarta.persistence.metamodel.Attribute.PersistentAttributeType;
@@ -727,18 +728,25 @@ private static jakarta.persistence.criteria.Order toJpaOrder(Order order, From<?
727728
PropertyPath property = PropertyPath.from(order.getProperty(), from.getJavaType());
728729
Expression<?> expression = toExpressionRecursively(from, property);
729730

730-
if (order.getNullHandling() != Sort.NullHandling.NATIVE) {
731-
throw new UnsupportedOperationException("Applying Null Precedence using Criteria Queries is not yet supported.");
732-
}
731+
Nulls nulls = toNulls(order.getNullHandling());
733732

734733
if (order.isIgnoreCase() && String.class.equals(expression.getJavaType())) {
735734
Expression<String> upper = cb.lower((Expression<String>) expression);
736-
return order.isAscending() ? cb.asc(upper) : cb.desc(upper);
735+
return order.isAscending() ? cb.asc(upper, nulls) : cb.desc(upper, nulls);
737736
} else {
738-
return order.isAscending() ? cb.asc(expression) : cb.desc(expression);
737+
return order.isAscending() ? cb.asc(expression, nulls) : cb.desc(expression, nulls);
739738
}
740739
}
741740

741+
private static Nulls toNulls(Sort.NullHandling nullHandling) {
742+
743+
return switch (nullHandling) {
744+
case NULLS_LAST -> Nulls.LAST;
745+
case NULLS_FIRST -> Nulls.FIRST;
746+
case NATIVE -> Nulls.NONE;
747+
};
748+
}
749+
742750
static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath property) {
743751
return toExpressionRecursively(from, property, false);
744752
}

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import jakarta.persistence.criteria.JoinType;
3535
import jakarta.persistence.criteria.Nulls;
3636
import jakarta.persistence.criteria.Path;
37+
import jakarta.persistence.criteria.Nulls;
3738
import jakarta.persistence.criteria.Root;
3839
import jakarta.persistence.spi.PersistenceProvider;
3940
import jakarta.persistence.spi.PersistenceProviderResolver;
@@ -353,8 +354,8 @@ void toOrdersCanSortByJoinColumn() {
353354
assertThat(orders).hasSize(1);
354355
}
355356

356-
@Test // GH-3529
357-
void nullPrecedenceThroughCriteriaApiNotYetSupported() {
357+
@Test // GH-3529, GH-3587
358+
void queryUtilsConsidersNullPrecedence() {
358359

359360
CriteriaBuilder builder = em.getCriteriaBuilder();
360361
CriteriaQuery<User> query = builder.createQuery(User.class);
@@ -363,8 +364,10 @@ void nullPrecedenceThroughCriteriaApiNotYetSupported() {
363364

364365
Sort sort = Sort.by(Sort.Order.desc("manager").nullsFirst());
365366

366-
assertThatExceptionOfType(UnsupportedOperationException.class)
367-
.isThrownBy(() -> QueryUtils.toOrders(sort, join, builder));
367+
List<jakarta.persistence.criteria.Order> orders = QueryUtils.toOrders(sort, join, builder);
368+
for (jakarta.persistence.criteria.Order order : orders) {
369+
assertThat(order.getNullPrecedence()).isEqualTo(Nulls.FIRST);
370+
}
368371
}
369372

370373
/**

0 commit comments

Comments
 (0)