我正在尝试使用Hibernate进行复杂的查询。我一直倾向于“标准”,但是我开始怀疑这是不可能的,因此任何建议都将有所帮助。
我有一个如下的实体结构:
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
现在,我希望能够采用一组属性/值对(字符串)并找到包含 所有 属性/值对的所有实例。在“值”中,只有attribute和localAttributeName中的一个为非空值,因此属性名称可以与localAttributeName或attribute.name匹配。最后使事情复杂化的是,值的唯一索引位于(实例,属性,值)或(实例,localAttributeName,值)上,也就是说,在实例中,任何给定的属性都可以具有多个值。
这是我到目前为止的内容:
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)
这样做的最佳方法是什么?
经过数小时的努力,我找到了解决方案。希望这对其他人有用。为了使此可行,我需要解决三个主要问题:
我在下面的代码中强调了其中的每一个。
首先,要摆脱异常,我发现子查询需要一个投影,下面突出显示。我只是对Instance的“ id”属性进行了投影。
其次,为了获得连接,我使用了Criteria.createCriteria()方法创建了一个左外部连接。因为在联接的不同级别上有多个条件,所以我必须保存联接的条件并将表达式分别附加到它们上。这让我在子查询中执行OR表达式。
最后,我必须添加一个eqProperty()子句以将子查询映射回主条件。就像它需要在结果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(); }