简体   繁体   English

关于集合值的Hibernate标准

[英]Hibernate criteria on collection values

I'm trying to put together a complicated query using Hibernate. 我正在尝试使用Hibernate组合一个复杂的查询。 I've been leaning toward Criteria, but I'm beginning to suspect it's not possible, and so any suggestions would be helpful. 我一直倾向于Criteria,但我开始怀疑它不可能,所以任何建议都会有所帮助。

I have an entity structure like the following: 我有一个如下的实体结构:

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;
}

These entities are related as you'd expect: 这些实体与您期望的相关:

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

Now, I would like to be able to take a set of attribute/value pairs (Strings) and find all instances that contain all of them. 现在,我希望能够获取一组属性/值对(字符串)并查找包含所有这些属性/值对的所有实例。 In Value, only one of attribute and localAttributeName are non-null, so the attribute name may match either localAttributeName or attribute.name. 在Value中,attribute和localAttributeName中只有一个是非null,因此属性名称可以匹配localAttributeName或attribute.name。 And to complicate things one last time, the unique index on Value is on (instance, attribute, value) or (instance, localAttributeName, value) -- that is, within an Instance, any given Attribute may have multiple Values. 最后一次使事情复杂化,Value上的唯一索引是(实例,属性,值)或(实例,localAttributeName,value) - 也就是说,在实例中,任何给定的Attribute都可能有多个值。

This is what I have so far: 这是我到目前为止:

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();
}

Based on the research I've done, what I've tried for that Do something section is: 基于我已经完成的研究,我尝试过的是做某事的部分是:

    // 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"));

But this throws the exception below, which I suspect is telling me I can't do this with Criteria, but I'd love to learn otherwise: 但是这引发了下面的例外情况,我怀疑它是告诉我我不能用Criteria做到这一点,但是我很乐意学习:

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

What would be the best way to go about doing this? 这样做最好的方法是什么?

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: 为了使这个可行,我需要解决三个要点:

  1. Add a Projection 添加投影
  2. Create the proper joins 创建正确的连接
  3. 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. 我刚刚对Instance的“id”属性进行了投影。

Second, to get the join, I used the Criteria.createCriteria() methods to create a left outer join. 其次,为了获得连接,我使用了Criteria.createCriteria()方法来创建左外连接。 Because I had multiple conditions at different levels of the join, I had to save the joined Criteria and attach expressions to them separately. 因为我在连接的不同级别有多个条件,所以我必须保存连接的Criteria并将表达式分别附加到它们。 This let me do my OR expression in the subquery. 这让我在子查询中做我的OR表达式。

Finally, I had to add an eqProperty() clause to map the subquery back to the main Criteria. 最后,我必须添加一个eqProperty()子句来将子查询映射回主Criteria。 Just like it would need to be in the resulting SQL, I used: instance.id = i.id. 就像它需要在结果SQL中一样,我使用了: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. 因为我已经将实例标准映射到“i”并且正在将此子句添加到Value Criteria,所以这转换为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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM