Подтвердить что ты не робот

Критерий спящего режима для значений коллекции

Я пытаюсь собрать сложный запрос, используя 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 только один из атрибутов и 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 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)

Каким будет лучший способ сделать это?

4b9b3361

Ответ 1

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

  • Добавить проекцию
  • Создайте правильные соединения
  • Правильно сопоставьте подзапрос с основными критериями

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

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