Hibernate order by with nulls last

核能气质少年 提交于 2019-11-27 07:48:01
Pascal Thivent

Given that HHH-465 is not fixed and is not going to get fixed in a near future for the reasons given by Steve Ebersole, your best option would be to use the CustomNullsFirstInterceptor attached to the issue either globally or specifically to alter the SQL statement.

I'm posting it below for the readers (credits to Emilio Dolce):

public class CustomNullsFirstInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = -3156853534261313031L;

    private static final String ORDER_BY_TOKEN = "order by";

    public String onPrepareStatement(String sql) {

        int orderByStart = sql.toLowerCase().indexOf(ORDER_BY_TOKEN);
        if (orderByStart == -1) {
            return super.onPrepareStatement(sql);
        }
        orderByStart += ORDER_BY_TOKEN.length() + 1;
        int orderByEnd = sql.indexOf(")", orderByStart);
        if (orderByEnd == -1) {
            orderByEnd = sql.indexOf(" UNION ", orderByStart);
            if (orderByEnd == -1) {
                orderByEnd = sql.length();
            }
        }
        String orderByContent = sql.substring(orderByStart, orderByEnd);
        String[] orderByNames = orderByContent.split("\\,");
        for (int i=0; i<orderByNames.length; i++) {
            if (orderByNames[i].trim().length() > 0) {
                if (orderByNames[i].trim().toLowerCase().endsWith("desc")) {
                    orderByNames[i] += " NULLS LAST";
                } else {
                    orderByNames[i] += " NULLS FIRST";
                }
            }
        }
        orderByContent = StringUtils.join(orderByNames, ",");
        sql = sql.substring(0, orderByStart) + orderByContent + sql.substring(orderByEnd); 
        return super.onPrepareStatement(sql);
    }

}

This feature has been implemented during Hibernate 4.2.x and 4.3.x releases as previously mentioned.

It can be used as for example:

Criteria criteria = ...;
criteria.addOrder( Order.desc( "name" ).nulls(NullPrecedence.FIRST) );

Hibernate v4.3 javadocs are less omissive here.

You can configure "nulls first" / "nulls last" in hibernate properties so it will be picked up by any criteria call by default: hibernate.order_by.default_null_ordering=last (or =first).

See this hibernate commit for details.

Matt

Here's my update to the class by (Pascal Thivent):

for (int i = 0; i < orderByNames.length; i++) {
    if (orderByNames[i].trim().length() > 0) {
        String orderName = orderByNames[i].trim().toLowerCase();
        if (orderName.contains("desc")) {
            orderByNames[i] = orderName.replace("desc", "desc NULLS LAST");
        } else {
            orderByNames[i] = orderName.replace("asc", "asc NULLS FIRST");
        }
    }
}

This fixes the problem:

This breaks if sql has limit/offset after order by – Sathish Apr 1 '11 at 14:52

Also here's how you can use this within JPA (hibernate):

Session session = entityManager.unwrap(Session.class);
Session nullsSortingProperlySession = null;
try {
    // perform a query guaranteeing that nulls will sort last
    nullsSortingProperlySession = session.getSessionFactory().withOptions()
        .interceptor(new GuaranteeNullsFirstInterceptor())
        .openSession();
} finally {
    // release the session, or the db connections will spiral
    try {
        if (nullsSortingProperlySession != null) {
            nullsSortingProperlySession.close();
        }
    } catch (Exception e) {
        logger.error("Error closing session", e);
    }
}

I've tested this on postgres and it fixes the 'nulls are higher than non-nulls' issue that we were having.

Another variant, if you create SQL on the fly and don't use Criteria API:

ORDER BY COALESCE(,'0') [ASC|DESC]

This works either for varchar or numeric columns.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!