čtvrtek 10. března 2011

JPA Queries - A Few Methods to Simplify the Life with JPA/JPQL

The JPA Query.getSingleResult() method is usable only when the JPQL has exactly one result. Yeah, you can catch NoResultException or NonUniqueResultException exceptions, but the try ... catch block make the code less readable. So I have coded three static method to overcome this JPA deficiency:
public class Queries {
    /**
     * @param query
     * @return {@code true}, the query has one or more results, otherwise returns {@code false}.
     */
    public static boolean hasAnyResult(Query query) {
        query.setMaxResults(1);
        final List<?> list = query.getResultList();
        return list.size() > 0;
    }

    /**
     * @param query
     * @return The only one result or {@code null}, when the query has no result.
     * @throws NonUniqueResultException
     *             when the query has more than one result.
     */
    public static Object singleResultOrNull(Query query) {
        query.setMaxResults(2);
        final List<?> list = query.getResultList();
        final int size = list.size();
        if (size <= 0) {
            return null;
        }
        if (size > 1) {
            throw new NonUniqueResultException("result returns more than one element");
        }

        // return first element from the list
        return list.get(0);
    }

    /**
     * @param query
     * @return The first result or {@code null}, when the query has no result.
     */
    public static Object firstResultOrNull(Query query) {
        query.setMaxResults(1);
        final List<?> list = query.getResultList();
        final int size = list.size();
        if (size <= 0) {
            return null;
        }
        if (size > 1) { // should not happened, Hibernate bug?
            Logger.getLogger(Queries.class).error("firstResultOrNull more rows returned. setMaxResults(1) does not work?");
        }

        return list.get(0);
    }

    /**
     * Constructor. No instances.
     */
    private Queries() {
    }
}
The hasAnyResult is the boolean query method - you may test if something exists in database. The best is to use it with some JPQL query returning only id of an entity, e.g.:
Queries.hasAnyResult(em.createQuery("SELECT id FROM Person WHERE ..."));
I think the usage of the singleResultOrNull is obvious. Use it, when you are sure the result returns one or no result. The exception NonUniqueResultException should warn you that something is wrong, wither the query or the data.

The firstResultOrNull returns just the first result of the query. Typically, use it with queries with "ORDER BY" statement, like "get the Person with lowest/highest salary."