Error de paginación con Hibernate + Criteria.DISTINCT_ROOT_ENTITY

Hibernate tiene en la clase org.hibernate.Criteria un par de métodos (setFirstResult y setMaxResults) que nos permiten realizar una paginación de los resultados a nivel de query SQL, es decir, solo obtiene de base de datos el número de resultados solicitados de manera que, en las paginaciones, no sobrecarga la base de datos solicitando datos que no se van a utilizar.

Por otro lado, cuando los objetos que queremos obtener mediante un criteria de hibernate tienen propiedades con relación “1 a n” que obtenemos de manera “eager”, es necesario establecer también en nuestra instancia de clase org.hibernate.Criteria la opción setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) de lo contrario obtendremos tantos objetos padre como hijos tenga dicho objeto padre en la relación “1 a n” que obtenemos de manera eager.

P.E. Si no empleamos la opción setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) y tenemos un objeto Persona relacionada con una lista de Coches, donde una persona puede tener una serie de coches, obtendríamos tantos objetos Persona como Coches tenga, eso sí, cada persona con la colección de objetos coche inicializada.

Cuando empleamos las dos técnicas juntas (paginación hibernate y distinct de la entidad raíz) la paginación falla, esto es debido a que hibernate lanza primero la consulta SQL limitando los resultados a los especificados en setMaxResults y posteriormente realiza el filtrado DISTINCT_ROOT_ENTITY, por lo cual aunque, por ejemplo, especifiquemos setMaxResults(20) e inicialmente obtengamos 20 resultados posteriormente el filtrado puede disminuir el número de resultados a 17.

La solución pasa por hacer dos consultas, una para obtener la paginación y otra para obtener los resultados con los “join” de los hijos “1 a n”.

public static List queryPersonas(Date queryDate, int intMaxResults,
		int offset) throws Exception {

	Criteria criteria = session.createCriteria(Persona.class);
	criteria.setFirstResult(offset).setMaxResults(intMaxResults);
	criteria.addOrder(Order.asc("nombre"));
	if (queryDate != null) {
		Criteria subCrit = criteria.createCriteria("coches", "coche");
		subCrit.add(Property.forName("fechaCompra").ge(queryDate));

		// Declarar distinct para los ids y
		// las columnas incluidas en la búsqueda

		criteria.setProjection(Projections.distinct(Projections
				.projectionList().add(Projections.id())
				.add(Projections.property("nombre"))));
		List list = criteria.list();

		// Obtener los ids del resultado (tipo lista de Object[])
		List idlist = new ArrayList();
		for (Iterator iditer = list.iterator(); iditer.hasNext();) {
			Object[] record = iditer.next();
			idlist.add((Long) record[0]);
		}
		// Hibernate no permite especificar
		// un Expression.in en el que la lista esté vacía
		if (idlist.size() > 0) {
			criteria = session.createCriteria(Persona.class);
			criteria.add(Property.forName("id").in(idlist));
		} else {
			return new ArrayList();
		}
	}

	return criteria.list();
}

Fuente.