12

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

ここで、属性/値のペア (文字列) のセットを取得し、それらすべてを含むすべてのインスタンスを検索できるようにしたいと考えています。Value では、attribute と localAttributeName の 1 つだけが非 null であるため、属性名は localAttributeName または attribute.name のいずれかと一致する可能性があります。さらに複雑なことに、Value の一意のインデックスは on (instance, attribute, value) または (instance, 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"));

しかし、これは以下の例外をスローします。これは、基準ではこれを行うことができないと言っていると思われますが、それ以外の方法で学びたいと思います:

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

これを行うための最良の方法は何ですか?

4

1 に答える 1

18

数時間叩いた後、解決策を見つけました。うまくいけば、これは他の人にも役立ちます。これを実現するために解決する必要があった主なポイントは次の 3 つです。

  1. プロジェクションを追加する
  2. 適切な結合を作成する
  3. サブクエリをメインの条件に適切にマップします

以下のコードでこれらのそれぞれを強調表示しました。

まず、例外を取り除くために、サブクエリにプロジェクションが必要であることを発見しました。インスタンスの「id」プロパティでプロジェクションを実行しました。

次に、結合を取得するために、Criteria.createCriteria() メソッドを使用して左外部結合を作成しました。結合のさまざまなレベルで複数の条件があったため、結合された基準を保存し、それらに式を個別に添付する必要がありました。これにより、サブクエリで OR 式を実行できます。

最後に、サブクエリをメインの Criteria にマップする 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();
}
于 2011-01-29T07:59:38.183 に答える