2011-01-29 20 views
12

Estoy tratando de armar una consulta complicada usando Hibernate. Me he estado inclinando por Criteria, pero estoy empezando a sospechar que no es posible, por lo que cualquier sugerencia sería útil.Criterios de Hibernación en los valores de recopilación

que tienen una estructura de entidad como el siguiente:

public class Attribute { 
    private Integer id; 
    private String name; 
    private Set<Value> values; 
} 

public class Instance { 
    private Integer id; 
    private int instanceRef; 
    private Set<Value> values; 
} 

public class Value { 
    private Integer id; 
    private Attribute attribute; 
    private String localAttributeName; 
    private Instance instance; 
    private String value; 
} 

Estas entidades están relacionadas como era de esperar:

value.attribute_id --> attribute.id 
value.instance_id --> instance.id 

Ahora, me gustaría ser capaz de tomar un conjunto de pares de atributo/valor (cadenas) y encontrar todas las instancias que contienen todas de ellas. En Value, solo uno de los atributos y localAttributeName no son nulos, por lo que el nombre del atributo puede coincidir con localAttributeName o attribute.name. Y para complicar las cosas una vez más, el índice único en Value está activado (instancia, atributo, valor) o (instancia, localAttributeName, value); es decir, dentro de una Instancia, cualquier Atributo dado puede tener múltiples Valores.

Esto es lo que tengo hasta ahora:

public List<Instance> getMatchingInstances(Map<String, String> attrValues) { 
    Criteria crit = session.createCriteria(Instance.class, "i"); 
    for(Map.Entry<String, String> entry : attrValues) { 
     DetachedCriteria valueCrit = DetachedCriteria.forClass(Value.class, "v"); 

     // Do something here with valueCrit 

     crit.add(Subqueries.exists(valueCrit)); 
    } 
    return crit.list(); 
} 

Sobre la base de la investigación que he hecho, lo que he tratado de esa sección de hacer algo es:

// This would only check localAttributeName and not attribute.name. 
    // That's okay -- once I get the rest to work, I can figure this out. 
    valueCrit.add(Restrictions.eq("localAttributeName", entry.getKey()); 
    valueCrit.add(Restrictions.eq("value", entry.getValue()); 
    valueCrit.add(Restrictions.eqProperty("v.instance_id", "i.id")); 

Pero esto arroja la excepción a continuación, que sospecho que me está diciendo que no puedo hacer esto con Criteria, pero me gustaría saber lo contrario:

java.lang.NullPointerException 
    at org.hibernate.loader.criteria.CriteriaQueryTranslator.getProjectedTypes(CriteriaQueryTranslator.java:341) 

¿Cuál sería la mejor manera de hacerlo?

Respuesta

17

Descubrí la solución después de unas horas de golpearla. Con suerte, esto es útil para otros. Había tres puntos principales que tenía que resolver para que esto sea posible:

  1. Añadir una proyección
  2. Crear el correcto une
  3. map correctamente la sub consulta de nuevo a los criterios principales

I He resaltado cada uno de estos en el siguiente código.

En primer lugar, para deshacerme de la excepción, descubrí que la subconsulta necesitaba una proyección, que se resalta a continuación. Acabo de hacer una proyección en la propiedad "id" de Instance.

En segundo lugar, para obtener la unión, utilicé los métodos Criteria.createCriteria() para crear una unión externa izquierda. Como tenía varias condiciones en diferentes niveles de la combinación, tuve que guardar los Criterios unidos y adjuntar expresiones a ellos por separado. Esto me permite hacer mi expresión OR en la subconsulta.

Finalmente, tuve que agregar una cláusula eqProperty() para asignar la subconsulta a los criterios principales. Al igual que debería estar en el SQL resultante, utilicé: instance.id = i.id. Como ya había asignado los Criterios de instancia a "i" y estaba agregando esta cláusula a los Criterios de valor, esto se tradujo al SQL: v.instance_id = i.id.

Aquí está el código de trabajo:

public List<Instance> getMatchingInstances(Map<String, String> attrValues) { 
    Criteria crit = session.createCriteria(Instance.class, "i"); 
    for(Map.Entry<String, String> entry : attrValues) { 
     String attrName = entry.getKey(); 
     String val = entry.getValue(); 

     // Create the subquery 
     DetachedCriteria valueCrit = DetachedCriteria.forClass(Value.class, "v"); 

     // Join the Attribute object (left outer join) 
     DetachedCriteria attrCrit = 
      valueCrit.createCriteria("attribute", CriteriaSpecification.LEFT_JOIN); 

     // Put together the OR statement on the Attribute joined criterion. 
     Criterion localAttr = Restrictions.eq("v.localAttributeName", attrName); 
     Criterion globalAttr = Restrictions.eq("name", attrName); 
     attrCrit.add(Restrictions.or(localAttr, globalAttr)); 

     // Simple column equality on the subquery criterion. 
     valueCrit.add(Restrictions.eq("value", val)); 

     // Map the subquery back to the outer query. 
     valueCrit.add(Restrictions.eqProperty("instance.id", "i.id")); 

     // Add the missing projection. 
     valueCrit.setProjection(Projections.property("id")); 

     // Add this subquery to the outer query. 
     crit.add(Subqueries.exists(valueCrit)); 
    } 
    return crit.list(); 
} 
+1

Funciona !!! .. Gracias. – Gaurav

+0

Hola @Jon, ¿podrías ayudarme con este problema? Aquí está el enlace http://stackoverflow.com/questions/22919886/hibernate-criteria-filtering-by-attributes-of-collection! ¡Gracias! Es sobre algo bastante similar! – Victor

+0

¿Para qué versión de Hibernate es buena esta respuesta? –

Cuestiones relacionadas