我使用 JPA criteria API 从数据库中获取记录。 我有实体 Record ,其字段 dateTime 可以为空。我会编码:
public List<Record> find(RecordFilter recordFilter, int page, int pageSize) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Record> criteriaQuery = criteriaBuilder.createQuery(Record.class);
Root<Record> recordRoot = criteriaQuery.from(Record.class);
/*
* JOINS. Left Joins are used for optional fields, or fields inside of the optional fields.
*/
Join<Record, Agency> recordAgencyJoin = recordRoot.join(RecordTable.FIELD_AGENCY);
//Some other joins
//This is where I had the problem.
applyOrderBy(criteriaQuery, criteriaBuilder, recordRoot);
/*
* Specify which columns to select and their order.
* criteriaQuery.multiselect(....);
*/
applyMultiSelect(recordRoot, recordAgencyJoin, /*other joins*/ criteriaQuery);
/*
* criteriaQuery.where(somePredicate);
*/
applyFilter(recordFilter, criteriaQuery, criteriaBuilder,
recordRoot, recordAgencyJoin /*, other joins*/);
TypedQuery<Record> query = entityManager.<Record>createQuery(criteriaQuery);
RepositoryUtils.applyPagination(query, page, pageSize);
return query.getResultList();
}
private void applyOrderBy(CriteriaBuilder criteriaBuilder, Root<Record> recordRoot, CriteriaQuery<Record> criteriaQuery) {
//Other fields to be added to the final sort.
Order dateTimeDescOrder = criteriaBuilder.desc(recordRoot.get(RecordTable.FIELD_DATE_TIME));
criteriaQuery.orderBy(dateTimeDescOrder /*, other orders by*/);
}
结果是,dateTimeField 为 NULL 的记录首先显示。 我使用 Postrgres 数据库。 我会回答这个问题,因为我找到了解决方案。 这是一个类似的帖子。 JPA Criteria 查询 API 和按 null 最后排序
我在这里给出了这个任务的答案。
首先,Postgres 默认情况下首先返回 null。
SELECT * FROM record ORDER BY date_time_field DESC;
https://stackoverflow.com/a/7621232/4587961
SELECT * FROM record ORDER BY date_time_field DESC NULLS LAST;
其次,我必须更改 applyOrderBy 方法
private void applyOrderBy(CriteriaBuilder criteriaBuilder, Root<Record> recordRoot, CriteriaQuery<Record> criteriaQuery) {
//In the class code
//private static final Date MIN_DATE = new Date(0L);
final Date MIN_DATE = new Date(0L);
//We treat records will NULL dateTimeField as if it was MIN_DATE.
Order dateTimeDescOrder = criteriaBuilder.desc(
//NULL values - last - WORKAROUND.
criteriaBuilder.coalesce(recordRoot.get(RecordTable.FIELD_DATE_TIME), MIN_DATE));
criteriaQuery.orderBy(dateTimeDescOrder);
}
注意,来自 hibernate-jpa-2.1 的 CriteriaBuilder。
/**
* Create an expression that returns null if all its arguments
* evaluate to null, and the value of the first non-null argument
* otherwise.
*
* @param x expression
* @param y value
*
* @return coalesce expression
*/
<Y> Expression<Y> coalesce(Expression<? extends Y> x, Y y);
JPA 规范中没有任何内容可以控制 NULL 的处理方式(并且所有 RDBMS 都有其默认值的偏好)。这在 JPQL(基于字符串的查询)或 Criteria API 中均不可用。通过 JPQL,所有主要 JPA 提供商都允许使用
NULLS [FIRST|LAST]
在订购条款中。对于 Criteria,您受到 Criteria API 的限制,因此一切皆有可能。我知道 DataNucleus JPA 提供了 JPA Criteria API 的自定义版本,允许指定
Order order = criteriaBuilder.asc(myExpression).nullsFirst();
但显然这是特定于该 JPA 提供商的。
JPA 或 Eclipselink 中没有任何内容可以在 CriteriaBuilder 中使用。
示例:
Order order1 = cBuilder.desc(cBuilder.selectCase().when(cBuilder.isNotNull(from.get('ColumnName1')), from.get('ColumnName1')).otherwise(from.get('ColumnName1')));
CQuery.orderBy(order1);
它会给你最好的答案
我将 JPA 与 eclipselink 和 Postgres 一起使用,这里是namedquery,它按预期工作。 这是摘录:
,@NamedQuery(name = "ServerPasswords.forTheServer",
query = "SELECT ss FROM ServerPasswords ss WHERE ss.fkIds = :IDServer AND ss.private = FALSE "
+ "ORDER BY ss.servce, ss.active DESC, ss.datefrom DESC NULLS LAST, ss.dateto NULLS LAST")
我会执行两种排序:首先,将列表拆分为 null 和非 null 块,然后在块内排序。
如果您使用参数类型
orderBy()
调用 List<Order>
方法,则可以移交后续搜索参数。
private void applyOrderBy(CriteriaBuilder criteriaBuilder, Root<Record> recordRoot, CriteriaQuery<Record> criteriaQuery) {
//Other fields to be added to the final sort.
List<Order> dateTimeDescOrder = Arrays.asList(
// 1. split list into chunks of FIELD_DATE_TIME == null and FIELD_DATE_TIME != null, with nulls last.
criteriaBuilder.desc(
criteriaBuilder.selectCase().when(recordRoot.get(RecordTable.FIELD_DATE_TIME).isNull(), 0).otherwise(1)),
// 2. subsequent sort within the chunks
criteriaBuilder.desc(recordRoot.get(RecordTable.FIELD_DATE_TIME)));
// order by order list
criteriaQuery.orderBy(dateTimeDescOrder);
}
在 hibernate 6.2.9.Final 中,您可以在
SqmSortSpecification
类中指定第一个/最后一个空值。这是片段:
import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
Order order = (criteriaBuilder.asc(myExpression) as SqmSortSpecification).nullPrecedence(NullPrecedence.LAST);