Skip to content

Commit e048b0c

Browse files
committed
Consider NULLS precedence using Sort for Criteria Queries.
Closes #3587
1 parent 36b5a7e commit e048b0c

File tree

2 files changed

+22
-12
lines changed

2 files changed

+22
-12
lines changed

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

+15-8
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;
@@ -292,9 +293,8 @@ public static String applySorting(String query, Sort sort, @Nullable String alia
292293
Set<String> selectionAliases = getFunctionAliases(query);
293294
selectionAliases.addAll(getFieldAliases(query));
294295

295-
String orderClauses = sort.stream()
296-
.map(order -> getOrderClause(joinAliases, selectionAliases, alias, order))
297-
.collect(Collectors.joining(", "));
296+
String orderClauses = sort.stream().map(order -> getOrderClause(joinAliases, selectionAliases, alias, order))
297+
.collect(Collectors.joining(", "));
298298

299299
builder.append(orderClauses);
300300

@@ -753,18 +753,25 @@ private static jakarta.persistence.criteria.Order toJpaOrder(Order order, From<?
753753
PropertyPath property = PropertyPath.from(order.getProperty(), from.getJavaType());
754754
Expression<?> expression = toExpressionRecursively(from, property);
755755

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

760758
if (order.isIgnoreCase() && String.class.equals(expression.getJavaType())) {
761759
Expression<String> upper = cb.lower((Expression<String>) expression);
762-
return order.isAscending() ? cb.asc(upper) : cb.desc(upper);
760+
return order.isAscending() ? cb.asc(upper, nulls) : cb.desc(upper, nulls);
763761
} else {
764-
return order.isAscending() ? cb.asc(expression) : cb.desc(expression);
762+
return order.isAscending() ? cb.asc(expression, nulls) : cb.desc(expression, nulls);
765763
}
766764
}
767765

766+
private static Nulls toNulls(Sort.NullHandling nullHandling) {
767+
768+
return switch (nullHandling) {
769+
case NULLS_LAST -> Nulls.LAST;
770+
case NULLS_FIRST -> Nulls.FIRST;
771+
case NATIVE -> Nulls.NONE;
772+
};
773+
}
774+
768775
static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath property) {
769776
return toExpressionRecursively(from, property, false);
770777
}

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import jakarta.persistence.criteria.From;
3333
import jakarta.persistence.criteria.Join;
3434
import jakarta.persistence.criteria.JoinType;
35+
import jakarta.persistence.criteria.Nulls;
3536
import jakarta.persistence.criteria.Root;
3637
import jakarta.persistence.spi.PersistenceProvider;
3738
import jakarta.persistence.spi.PersistenceProviderResolver;
@@ -314,8 +315,8 @@ void toOrdersCanSortByJoinColumn() {
314315
assertThat(orders).hasSize(1);
315316
}
316317

317-
@Test // GH-3529
318-
void nullPrecedenceThroughCriteriaApiNotYetSupported() {
318+
@Test // GH-3529, GH-3587
319+
void queryUtilsConsidersNullPrecedence() {
319320

320321
CriteriaBuilder builder = em.getCriteriaBuilder();
321322
CriteriaQuery<User> query = builder.createQuery(User.class);
@@ -324,8 +325,10 @@ void nullPrecedenceThroughCriteriaApiNotYetSupported() {
324325

325326
Sort sort = Sort.by(Sort.Order.desc("manager").nullsFirst());
326327

327-
assertThatExceptionOfType(UnsupportedOperationException.class)
328-
.isThrownBy(() -> QueryUtils.toOrders(sort, join, builder));
328+
List<jakarta.persistence.criteria.Order> orders = QueryUtils.toOrders(sort, join, builder);
329+
for (jakarta.persistence.criteria.Order order : orders) {
330+
assertThat(order.getNullPrecedence()).isEqualTo(Nulls.FIRST);
331+
}
329332
}
330333

331334
/**

0 commit comments

Comments
 (0)