criteri di Hibernate sui valori di raccolta
-
27-10-2019 - |
Domanda
Sto cercando di mettere insieme una query complessa usando Hibernate. Ho sporgendosi verso criteri, ma comincio a sospettare che non è possibile, e così tutti i suggerimenti sarebbe utile.
Ho una struttura di entità come la seguente:
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;
}
Queste entità sono correlati come ci si aspetterebbe:
value.attribute_id --> attribute.id
value.instance_id --> instance.id
Ora, mi piacerebbe essere in grado di prendere una serie di coppie attributo / valore (stringhe) e trovare tutte le istanze che contengono tutti di loro. In Valore, solo uno di attributo e localAttributeName sono non nullo, così il nome attributo può corrispondere sia localAttributeName o attribute.name. E per le cose complicate per l'ultima volta, l'indice univoco sul Valore è acceso (esempio, l'attributo, valore) o (esempio, localAttributeName, value) -. Che è, all'interno di un grado, un dato attributo può avere più valori
Questo è quello che ho finora:
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();
}
Sulla base della ricerca che ho fatto, che cosa ho provato per quel Do sezione qualcosa è:
// 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"));
Ma questo genera l'eccezione di sotto, che ho il sospetto mi sta dicendo che non posso farlo con criteri, ma mi piacerebbe imparare altrimenti:
java.lang.NullPointerException
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getProjectedTypes(CriteriaQueryTranslator.java:341)
Quale sarebbe il modo migliore per andare a fare questo?
Soluzione
I figured out the solution after a few hours of banging on it. Hopefully, this is of use to others. There were three main points that I needed to solve to make this feasible:
- Add a Projection
- Create the proper joins
- Properly map the subquery back to the main criteria
I've highlighted each of these in the below code.
First, to get rid of the exception, I discovered that the subquery needed a projection, highlighted below. I just did a projection on the "id" property of Instance.
Second, to get the join, I used the Criteria.createCriteria() methods to create a left outer join. Because I had multiple conditions at different levels of the join, I had to save the joined Criteria and attach expressions to them separately. This let me do my OR expression in the subquery.
Finally, I had to add an eqProperty() clause to map the subquery back to the main Criteria. Just like it would need to be in the resulting SQL, I used: instance.id = i.id. Because I had already mapped the Instance Criteria to "i" and was adding this clause to the Value Criteria, this translated to the SQL: v.instance_id = i.id.
Here's the working code:
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();
}