This is a bit of an extension to katsu's answer to his own question. I was trying to find a solution to being able to sort most of the columns of a table where some columns are allowed to have null values. I wanted to sort the null values in front of the lowest non-null values when sorting in ascending order and after the lowest non-null values when sorting in descending order. In other words, pretty much the opposite of the (Oracle's) default behavior.
I found other methods that might do this, but this one didn't require me to go outside of Hibernate and JPA 2 persistence, but still get the results I wanted. This is a snippet of code taken from my actual code, but consolidated in one spot and with some names changed. Any syntax, compilation-type errors you see are probably due to that.
// sortByColumn is a String containing the Hibernate version of the column name, which had
// been assigned as the ID of the table header column of the column by which we are sorting.
// sortAscending is a Boolean object containing Boolean.TRUE if we are to sort in ascending
// order or Boolean.FALSE or null if we are to sort in descending order. This may seem a
// bit odd, but in the case we need this for, the default sort column is a release date and
// reverse chronological order is the most useful in that case.
// Also defined are: CriteriaQuery<SoftwareVersion> criteriaQuery and
// CriteriaBuilder criteriaBuilder by the typical means.
final Root<SoftwareVersion> softwareVersionRoot =
criteriaQuery.from(SoftwareVersion.class);
private static final String EMPTY_STRING = "";
if (sortByColumn != null && sortByColumn.trim().length() > 0) {
Order sortOrder;
Expression<String> sortColumnExpression;
if (sortByColumn.equalsIgnoreCase(SoftwareVersion_.installationFileLength.getName()) ||
sortByColumn.equalsIgnoreCase(SoftwareVersion_.releaseTimestamp.getName())) {
// The two non-String fields (exposed to the user) that we don't need to have the
// lower() function operate upon.
sortColumnExpression = oemSoftwareVersionRoot.get(sortByColumn);
} else {
// We use the lower() function to enforce case insensitive sorting on the columns we
// show to the user, which are all Strings except as noted above.
Expression<String> rootExpression = oemSoftwareVersionRoot.get(sortByColumn);
sortColumnExpression = criteriaBuilder.lower(rootExpression);
}
// The columns for installation file name, installation file length and release timestamp
// are just three of the columns that we allow the user to sort by. However, these three
// may have null values in the database, and require some special handling.
if (sortByColumn.equalsIgnoreCase(SoftwareVersion_.installationFileLength.getName()) ||
sortByColumn.equalsIgnoreCase(SoftwareVersion_.installationFileName.getName()) ||
sortByColumn.equalsIgnoreCase(SoftwareVersion_.releaseTimestamp.getName())
) {
Expression<Object> queryCase;
if (sortByColumn.equalsIgnoreCase(SoftwareVersion_.installationFileName.getName())) {
// Installation file name is a (case insensitive) String
queryCase = criteriaBuilder.selectCase().when(
criteriaBuilder.isNull(sortColumnExpression),
StringUtil.EMPTY_STRING).otherwise(sortColumnExpression);
} else if (sortByColumn.equalsIgnoreCase(SoftwareVersion_.releaseTimestamp.getName())) {
// Release timestamp is a database timestamp
LocalDateTime dateTime = LocalDateTime.of(1970,1,1,0,0);
// Equivalent to Unix epoch time. Note month is 1-12, not 0-11
queryCase = criteriaBuilder.selectCase().when(
criteriaBuilder.isNull(sortColumnExpression),
Timestamp.valueOf(dateTime)).otherwise(sortColumnExpression);
} else {
// Installation file length is a Long (or BigDecimal) computed when the file is uploaded.
// The user can't set or change it, but can sort by it.
queryCase = criteriaBuilder.selectCase().when(
criteriaBuilder.isNull(sortColumnExpression),
Long.valueOf(0)).otherwise(sortColumnExpression);
}
if (asc != null && asc.booleanValue()) {
sortOrder = criteriaBuilder.asc(queryCase);
} else {
sortOrder = criteriaBuilder.desc(queryCase);
}
} else {
if (asc != null && asc.booleanValue()) {
sortOrder = criteriaBuilder.asc(sortColumnExpression);
} else {
sortOrder = criteriaBuilder.desc(sortColumnExpression);
}
}
criteriaQuery.orderBy(sortOrder);
}