Question

Assuming I have classes User and UserGroup. There is an optional 1-many group to user association and the association is mapped from both sides (the UserGroup side via a property called "members", which is a HashSet, the User side via property "group").

Using the Criteria API, how can I query for all groups, sorted by count of group members?

(Edit) I should have pointed out when I asked this that pagination was being performed in the SQL, so the ordering should also be performed in the SQL if possible.

Was it helpful?

Solution

Another option would be to use the @Formula annotation which is basically the equivalent of creating a nested select so you can get at your counts.

On your DB objects property / member in question (which you should create), add the annotation to the getter like:

@Formula("(SELECT COUNT(TABLE_NAME.ID) FROM TABLE WHERE whatever...)") public long getNewProperty{ return newProperty; }

Then target the member directly to get hold of your count values...

Note: Be aware that when specifying the sql within the formula annotation, you must specify the field names directly when referencing the current objects table (this) as hibernate deals with this itself. So in your WHERE clause just use ID or whatever on its own when referencing the current objects table which I suspect is UserGroup.

I had to have a setter for my new property in order for hibernate to work and prevent the compiler from complaining.

There may be other clever ways of using @Formula, but I'm quite new to this and there doesn't appear to be very much documentation on the subject...

If you've never used nested SELECTS before, it might be an idea for you to just recreate in sql first - that helped me.

Hope this helps

OTHER TIPS

It is not possible by default using Citeria API but you can extend org.hibernate.criterion.Order class. This article is about how to extend that class: http://blog.tremend.ro/2008/06/10/how-to-order-by-a-custom-sql-formulaexpression-when-using-hibernate-criteria-api

My sollution is:

public class SizeOrder extends Order {

    protected String propertyName;
    protected boolean ascending;

    protected SizeOrder(String propertyName, boolean ascending) {
        super(propertyName, ascending);
        this.propertyName = propertyName;
        this.ascending = ascending;
    }

    public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
        String role = criteriaQuery.getEntityName(criteria, propertyName) + '.' + criteriaQuery.getPropertyName(propertyName);
        QueryableCollection cp = (QueryableCollection) criteriaQuery.getFactory().getCollectionPersister(role);

        String[] fk = cp.getKeyColumnNames();
        String[] pk = ((Loadable) cp.getOwnerEntityPersister())
                .getIdentifierColumnNames();
        return " (select count(*) from " + cp.getTableName() + " where "
                + new ConditionFragment()
                        .setTableAlias(
                                criteriaQuery.getSQLAlias(criteria, propertyName)
                        ).setCondition(pk, fk)
                    .toFragmentString() + ") "
                + (ascending ? "asc" : "desc");
    }

    public static SizeOrder asc(String propertyName) {
        return new SizeOrder(propertyName, true);
    }
    public static SizeOrder desc(String propertyName) {
        return new SizeOrder(propertyName, false);
    }
}

The toSqlString method is based on org.hibernate.criterion.SizeExpression class. (Source: http://javasourcecode.org/html/open-source/hibernate/hibernate-3.6.0.Final/org/hibernate/criterion/SizeExpression.java.html)

Example usage:

hibernateSession.createCriteria(UserGroup.class).addOrder( SizeOrder.asc("members") ).list();

this will list user groups ordered by the size of the members ascending, where "members" is the User collection in UserGroup entity.

You could probably so this by defining a "derived property" on your entity, which would be a read-only property which returns the size of the collection inside that entity. The easiest way to define a derived property is documented here:

update, insert (optional - defaults to true): specifies that the mapped columns should be included in SQL UPDATE and/or INSERT statements. Setting both to false allows a pure "derived" property whose value is initialized from some other property that maps to the same column(s), or by a trigger or other application.

Once the property is defined, you should be able to use the property name as an order clause in the criteria API, just like any other property. I haven't tried this myself, though.

If it works, it'll likely be done as an in-memory sort, since it won't be able to map it to SQL. If this is a problem, then the same link above documents how to implement a derived property using a custom SQL fragment:

A powerful feature is derived properties. These properties are by definition read-only. The property value is computed at load time. You declare the computation as an SQL expression. This then translates to a SELECT clause subquery in the SQL query that loads an instance:

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top