Критерии гибернации для коллекционных ценностей - PullRequest
12 голосов
/ 29 января 2011

Я пытаюсь собрать сложный запрос, используя 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.И чтобы усложнить ситуацию в последний раз, включается уникальный индекс Value (экземпляр, атрибут, значение) или (экземпляр, 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"));

Но ниже приведено исключение, которое, как я подозреваю, говорит мне, что я не могу сделать это с Критериями, но я бы хотел узнать иначе:

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

Как лучше всего поступить?это?

1 Ответ

18 голосов
/ 29 января 2011

Я разобрался с решением после нескольких часов стучать по нему.Надеюсь, это пригодится другим.Чтобы сделать это возможным, мне нужно было решить три основных момента:

  1. Добавить проекцию
  2. Создать правильные объединения
  3. Правильно отобразить подзапрос обратно наосновные критерии

Я выделил каждый из них в приведенном ниже коде.

Сначала, чтобы избавиться от исключения, я обнаружил, что подзапросу нужна проекция, выделенная ниже.Я только что сделал проекцию на свойство «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();
}
...