2011-04-06 19 views
8

Uso de hibernate ctiteria Deseo seleccionar un objeto y está asociado oneToMany lista de objetos. Quiero paginar a través de esta lista para evitar la temida cuestión de selección hibernate n + 1Criterios de hibernación n + 1 problema con maxresults

Aquí hay una solución de trabajo que requiere 11 viajes a la base de datos para 10 objetos principales.

Criteria criteria = this.getSession().createCriteria(Mother.class); 
criteria.addOrder(Order.asc("title")) 
.setMaxResults(details.getMaxRows()) 
.setFirstResult(details.getStartResult()) 
.setFetchMode("kittens", FetchMode.SELECT); 
List test = criteria.list(); 

Y he aquí una solución que ejecuta una sola instrucción SQL (hurra), pero no puede manejar la paginación es decir, los setMaxResults y setFirstResult son incorrectos en el objeto padre de la madre (Boo)

Criteria criteria = this.getSession().createCriteria(Mother.class); 
criteria.addOrder(Order.asc("title")) 
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) 
.setMaxResults(details.getMaxRows()) 
.setFirstResult(details.getStartResult()) 
.setFetchMode("kittens", FetchMode.JOIN); 
List test = criteria.list(); 

Esto parece como tales un requisito común, pero he buscado una solución sin suerte.

¿Alguno de los interesados?

Respuesta

15

Hacer las cosas por debajo de 1 consulta es dura (es decir, no sé una solución portátil), pero conseguir que se reduce a 2 consultas (con independencia de n) es bastante simple:

Criteria criteria = this.getSession().createCriteria(Mother.class); 
criteria.addOrder(Order.asc("title")) 
    .setMaxResults(details.getMaxRows()) 
    .setFirstResult(details.getStartResult()) 
    .setProjection(Projections.id()); 
List<?> ids = criteria.list(); 

criteria = getSession().createCriteria(Mother.class) 
    .add(Restrictions.in("id", ids)) 
    .setFetchMode("children", FetchMode.JOIN) 
    .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) 

return criteria.list(); 

para algunas bases de datos, ir a buscar subselección children podría funcionar, también.

+0

Gran respuesta. Gracias y respeto – jaseFace

+0

Ojalá pudiera votar esto más de una vez. Solución inteligente. – cdmckay

+0

Gran andwer; en mi caso obtuve identificaciones repetidas debido a la unión de uno a muchos, así que reemplacé la proyección en la línea 5 con '.setProjection (Projections.distinct (Projections.id()))' –

2

Por lo que yo sé no hay buenas maneras de resolver este problema, excepto para el siguiente truco con la consulta SQL nativo (sintaxis SQL exacta depende de su DBMS):

List<Mother> result = s.createSQLQuery(
    "select {m.*}, {k.*} " + 
    "from (select limit :firstResult :maxResults * from Mother m) m " + 
    "left join Kitten k on k.motherId = m.id" 
    ) 
    .addEntity("m", Mother.class) 
    .addJoin("k", "m.kittens") 
    .setParameter("firstResult", ...) 
    .setParameter("maxResults", ...) 
    .setResultTransformer(MyDistrictRootEntityResultTransformer.INSTANCE) 
    .list(); 

... 

// Unfortunately built-in DistrictRootEntityResultTransformer cannot be used 
// here, since it assumes that root entity is the last in the tuple, whereas 
// with addEntity()/addJoin() it's the first in the tuple 
public class MyDistrictRootEntityResultTransformer implements ResultTransformer { 
    public static final MyDistrictRootEntityResultTransformer INSTANCE = new MyDistrictRootEntityResultTransformer(); 

    public Object transformTuple(Object[] tuple, String[] aliases) { 
     return tuple[0]; 
    } 

    public List transformList(List collection) { 
     return DistinctResultTransformer.INSTANCE.transformList(collection); 
    } 
} 
+0

Es la mejor solución. Solo una llamada SQL. – IvanNik

Cuestiones relacionadas