diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java
index b38449c003..d4abd3084b 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java
@@ -743,4 +743,20 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
@Override
JpaOrder desc(Expression> x);
+
+ /**
+ * Create an ordering by the ascending value of the expression.
+ * @param x expression used to define the ordering
+ * @param nullsFirst Whether null
should be sorted first
+ * @return ascending ordering corresponding to the expression
+ */
+ JpaOrder asc(Expression> x, boolean nullsFirst);
+
+ /**
+ * Create an ordering by the descending value of the expression.
+ * @param x expression used to define the ordering
+ * @param nullsFirst Whether null
should be sorted first
+ * @return descending ordering corresponding to the expression
+ */
+ JpaOrder desc(Expression> x, boolean nullsFirst);
}
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java
index 83098726ea..147acbef5a 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java
@@ -63,6 +63,7 @@ import org.hibernate.query.criteria.JpaCoalesce;
import org.hibernate.query.criteria.JpaCompoundSelection;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaExpression;
+import org.hibernate.query.criteria.JpaOrder;
import org.hibernate.query.criteria.JpaParameterExpression;
import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.criteria.ValueHandlingMode;
@@ -397,6 +398,24 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return new SqmSortSpecification( (SqmExpression>) x, SortOrder.DESCENDING );
}
+ @Override
+ public JpaOrder asc(Expression> x, boolean nullsFirst) {
+ return new SqmSortSpecification(
+ (SqmExpression>) x,
+ SortOrder.ASCENDING,
+ nullsFirst ? NullPrecedence.FIRST : NullPrecedence.LAST
+ );
+ }
+
+ @Override
+ public JpaOrder desc(Expression> x, boolean nullsFirst) {
+ return new SqmSortSpecification(
+ (SqmExpression>) x,
+ SortOrder.DESCENDING,
+ nullsFirst ? NullPrecedence.FIRST : NullPrecedence.LAST
+ );
+ }
+
@Override
public JpaCompoundSelection tuple(Selection>[] selections) {
//noinspection unchecked
diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java
new file mode 100644
index 0000000000..8e8d3c660a
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/NullPrecedenceTest.java
@@ -0,0 +1,92 @@
+package org.hibernate.query.criteria.internal;
+
+import java.util.List;
+
+import org.hibernate.query.criteria.HibernateCriteriaBuilder;
+
+import org.hibernate.testing.TestForIssue;
+import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
+import org.hibernate.testing.orm.junit.Jpa;
+
+import org.junit.jupiter.api.Test;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.TypedQuery;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Root;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+/**
+ * @author Christian Beikov
+ */
+@TestForIssue(jiraKey = "HHH-14897")
+@Jpa(
+ annotatedClasses = { NullPrecedenceTest.Foo.class }
+)
+public class NullPrecedenceTest {
+
+ @Test
+ public void testNullPrecedence(EntityManagerFactoryScope scope) {
+ scope.inTransaction( entityManager -> {
+ entityManager.persist( new Foo( 1L, null ) );
+ entityManager.persist( new Foo( 2L, "ABC" ) );
+ entityManager.persist( new Foo( 3L, "DEF" ) );
+ entityManager.persist( new Foo( 4L, "DEF" ) );
+ final HibernateCriteriaBuilder cb = (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder();
+
+ final CriteriaQuery cq = cb.createQuery( Foo.class );
+ final Root foo = cq.from( Foo.class );
+
+ cq.orderBy(
+ cb.desc( foo.get( "bar" ), true ),
+ cb.desc( foo.get( "id" ) )
+ );
+
+ final TypedQuery tq = entityManager.createQuery( cq );
+
+ final List resultList = tq.getResultList();
+ assertEquals( 4, resultList.size() );
+ assertEquals( 1L, resultList.get( 0 ).getId() );
+ assertEquals( 4L, resultList.get( 1 ).getId() );
+ assertEquals( 3L, resultList.get( 2 ).getId() );
+ assertEquals( 2L, resultList.get( 3 ).getId() );
+ } );
+ }
+
+ @Entity(name = "Foo")
+ public static class Foo {
+
+ private long id;
+ private String bar;
+
+ public Foo() {
+ }
+
+ public Foo(long id, String bar) {
+ this.id = id;
+ this.bar = bar;
+ }
+
+ @Id
+ @Column(nullable = false)
+ public long getId() {
+ return this.id;
+ }
+
+ public void setId(final long id) {
+ this.id = id;
+ }
+
+ public String getBar() {
+ return bar;
+ }
+
+ public void setBar(String bar) {
+ this.bar = bar;
+ }
+ }
+}