我正在尝试使用Hibernate组合一个复杂的查询。我一直倾向于Criteria,但我开始怀疑这是不可能的,所以任何建议都会有所帮助。
我有如下的实体结构:
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;
}
这些实体与您期望的相关:
value.attribute_id --> attribute.id
value.instance_id --> instance.id
现在,我希望能够获取一组属性/值对(字符串)并查找包含所有的所有实例。在Value中,attribute和localAttributeName中只有一个是非null,因此属性名称可以匹配localAttributeName或attribute.name。最后一次使事情复杂化,Value上的唯一索引是(实例,属性,值)或(实例,localAttributeName,value) - 也就是说,在实例中,任何给定的属性都可能有多个值。
这是我到目前为止所做的:
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();
}
根据我所做的研究,我尝试过的是做某事的部分:
// 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"));
但是这引发了下面的例外情况,我怀疑它是在告诉我,我不能用Criteria做到这一点,但我很乐意另外学习:
java.lang.NullPointerException
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getProjectedTypes(CriteriaQueryTranslator.java:341)
这样做最好的方法是什么?
答案 0 :(得分:18)
经过几个小时的敲击,我找到了解决方案。希望这对其他人有用。为实现这一目标,我需要解决三个要点:
我在下面的代码中突出显示了这些内容。
首先,为了摆脱异常,我发现子查询需要一个投影,下面突出显示。我刚刚对Instance的“id”属性做了一个投影。
其次,为了获得连接,我使用了Criteria.createCriteria()方法来创建左外连接。因为我在连接的不同级别有多个条件,所以我必须保存连接的Criteria并将表达式分别附加到它们。这让我在子查询中做我的OR表达式。
最后,我必须添加一个eqProperty()子句来将子查询映射回主Criteria。就像它需要在结果SQL中一样,我使用了:instance.id = i.id.因为我已经将实例标准映射到“i”并且正在将此子句添加到值标准,所以这转换为SQL:v.instance_id = i.id。
这是工作代码:
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();
}