HHH-14897 Allow ordering with nulls first/last in JPA Criteria
This commit is contained in:
parent
69cd716e37
commit
2d871d64f2
|
@ -9,6 +9,7 @@ package org.hibernate.query.criteria;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
import javax.persistence.criteria.Expression;
|
import javax.persistence.criteria.Expression;
|
||||||
|
import javax.persistence.criteria.Order;
|
||||||
import javax.persistence.criteria.Predicate;
|
import javax.persistence.criteria.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,4 +68,20 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
||||||
*/
|
*/
|
||||||
<M extends Map<?,?>> Expression<Integer> mapSize(M map);
|
<M extends Map<?,?>> Expression<Integer> mapSize(M map);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an ordering by the ascending value of the expression.
|
||||||
|
* @param x expression used to define the ordering
|
||||||
|
* @param nullsFirst Whether <code>null</code> should be sorted first
|
||||||
|
* @return ascending ordering corresponding to the expression
|
||||||
|
*/
|
||||||
|
Order 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 <code>null</code> should be sorted first
|
||||||
|
* @return descending ordering corresponding to the expression
|
||||||
|
*/
|
||||||
|
Order desc(Expression<?> x, boolean nullsFirst);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,8 +250,16 @@ public class CriteriaBuilderImpl implements HibernateCriteriaBuilder, Serializab
|
||||||
return new OrderImpl( x, false );
|
return new OrderImpl( x, false );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Order asc(Expression<?> x, boolean nullsFirst) {
|
||||||
|
return new OrderImpl( x, true, nullsFirst );
|
||||||
|
}
|
||||||
|
|
||||||
// predicates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
@Override
|
||||||
|
public Order desc(Expression<?> x, boolean nullsFirst) {
|
||||||
|
return new OrderImpl( x, false, nullsFirst );
|
||||||
|
}
|
||||||
|
// predicates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
public Predicate wrap(Expression<Boolean> expression) {
|
public Predicate wrap(Expression<Boolean> expression) {
|
||||||
if ( Predicate.class.isInstance( expression ) ) {
|
if ( Predicate.class.isInstance( expression ) ) {
|
||||||
|
|
|
@ -395,6 +395,17 @@ public class CriteriaQueryImpl<T> extends AbstractNode implements CriteriaQuery<
|
||||||
jpaqlBuffer.append( sep )
|
jpaqlBuffer.append( sep )
|
||||||
.append( ( (Renderable) orderSpec.getExpression() ).render( renderingContext ) )
|
.append( ( (Renderable) orderSpec.getExpression() ).render( renderingContext ) )
|
||||||
.append( orderSpec.isAscending() ? " asc" : " desc" );
|
.append( orderSpec.isAscending() ? " asc" : " desc" );
|
||||||
|
if ( orderSpec instanceof OrderImpl ) {
|
||||||
|
Boolean nullsFirst = ( (OrderImpl) orderSpec ).getNullsFirst();
|
||||||
|
if ( nullsFirst != null ) {
|
||||||
|
if ( nullsFirst ) {
|
||||||
|
jpaqlBuffer.append( " nulls first" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jpaqlBuffer.append( " nulls last" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
sep = ", ";
|
sep = ", ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,20 @@ public class OrderImpl implements Order, Serializable {
|
||||||
|
|
||||||
private final Expression<?> expression;
|
private final Expression<?> expression;
|
||||||
private final boolean ascending;
|
private final boolean ascending;
|
||||||
|
private final Boolean nullsFirst;
|
||||||
|
|
||||||
public OrderImpl(Expression<?> expression) {
|
public OrderImpl(Expression<?> expression) {
|
||||||
this( expression, true );
|
this( expression, true, null );
|
||||||
}
|
}
|
||||||
|
|
||||||
public OrderImpl(Expression<?> expression, boolean ascending) {
|
public OrderImpl(Expression<?> expression, boolean ascending) {
|
||||||
|
this(expression, ascending, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderImpl(Expression<?> expression, boolean ascending, Boolean nullsFirst) {
|
||||||
this.expression = expression;
|
this.expression = expression;
|
||||||
this.ascending = ascending;
|
this.ascending = ascending;
|
||||||
|
this.nullsFirst = nullsFirst;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Order reverse() {
|
public Order reverse() {
|
||||||
|
@ -40,4 +46,8 @@ public class OrderImpl implements Order, Serializable {
|
||||||
public Expression<?> getExpression() {
|
public Expression<?> getExpression() {
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getNullsFirst() {
|
||||||
|
return nullsFirst;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package org.hibernate.query.criteria.internal;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Beikov
|
||||||
|
*/
|
||||||
|
@TestForIssue( jiraKey = "HHH-14897" )
|
||||||
|
public class NullPrecedenceTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] { Foo.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNullPrecedence() {
|
||||||
|
doInJPA( this::entityManagerFactory, 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<Foo> cq = cb.createQuery( Foo.class );
|
||||||
|
final Root<Foo> foo = cq.from( Foo.class );
|
||||||
|
|
||||||
|
cq.orderBy(
|
||||||
|
cb.desc( foo.get( "bar" ), true ),
|
||||||
|
cb.desc( foo.get( "id" ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
final TypedQuery<Foo> tq = entityManager.createQuery( cq );
|
||||||
|
|
||||||
|
final List<Foo> resultList = tq.getResultList();
|
||||||
|
Assert.assertEquals( 4, resultList.size() );
|
||||||
|
Assert.assertEquals( 1L, resultList.get( 0 ).getId() );
|
||||||
|
Assert.assertEquals( 4L, resultList.get( 1 ).getId() );
|
||||||
|
Assert.assertEquals( 3L, resultList.get( 2 ).getId() );
|
||||||
|
Assert.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue