Сложные проекции гибернации

Я хочу спросить, возможно ли, что я создаю прогнозы запросов и критерии для более чем одного уровня? У меня есть 2 модельных класса:

@Entity  
@Table(name = "person")  
public class Person implements Serializable {
    @Id
    @GeneratedValue
    private int personID;
    private double valueDouble;
    private int valueInt;
    private String name;
    @OneToOne(cascade = {CascadeType.ALL}, orphanRemoval = true)
    @JoinColumn(name="wifeId")
    private Wife wife;
       /*   
        *  Setter Getter    
        */
}


@Entity 
@Table(name = "wife")  
public class Wife implements Serializable {

    @Id
    @GeneratedValue     
    @Column(name="wifeId")
    private int id;
    @Column(name="name")
    private String name;
    @Column(name="age")
    private int age;            
    /*
     *  Setter Getter
     */       
}

API My Criteria:

ProjectionList projections = Projections.projectionList(); 
projections.add(Projections.property("this.personID"), "personID");
projections.add(Projections.property("this.wife"), "wife");
projections.add(Projections.property("this.wife.name"), "wife.name");

Criteria criteria = null; 
criteria = getHandlerSession().createCriteria(Person.class); 
criteria.createCriteria("wife", "wife", JoinType.LEFT.ordinal()); 
criterion = Restrictions.eq("wife.age", 19);  
criteria.add(criterion); 
criteria.setProjection(projections);
criteria.setResultTransformer(Transformers.aliasToBean(Person.class)); 
return criteria.list();

и я надеюсь, что смогу запросить Person с указанными критериями для свойства жены и заданным возвращаемым результатом. поэтому я использовал проекции для получения указанного возвращаемого результата.

Я хочу, чтобы personID, имя (Person), имя (Wife) были возвращены. как API я должен использовать, я больше предпочитаю использовать Hibernate Criteria API.

На этот раз я использовал приведенный выше код для получения ожидаемого результата, но он выдаст исключение с сообщением об ошибке: Exception in thread "main" org.hibernate.QueryException: could not resolve property: wife.name of: maladzan.model.Personи будь моим Restrictions.eq("wife.age", 19); правильно для того, чтобы получить человека, у которого есть жена с 19 как ее возрастное значение?

Спасибо

4 ответа

AFAIK невозможно спроецировать более одного уровня в глубину с помощью алиасового бобового трансформатора. Ваши варианты

  • создать плоский объект передачи данных (DTO)
  • заполнить полученную личность в памяти самостоятельно
  • реализовать собственный преобразователь результатов (аналогично варианту 2)

Вариант 1 выглядит так:

Criteria criteria = getHandlerSession().createCriteria(Person.class)
    .createAlias("wife", "wife", JoinType.LEFT.ordinal())
    .add(Restrictions.eq("wife.age", 19)); 
    .setProjection(Projections.projectionList()
        .add(Projections.property("personID"), "personID")
        .add(Projections.property("name"), "personName")
        .add(Projections.property("wife.name"), "wifeName"));
    .setResultTransformer(Transformers.aliasToBean(PersonWifeDto.class));

return criteria.list();

Я написал ResultTransformer, это делает это точно. Его имя AliasToBeanNestedResultTransformerПроверьте это на github.

Спасибо Сами Андони. Я смог использовать ваш AliasToBeanNestedResultTransformer с незначительными изменениями в соответствии с моей ситуацией. Я обнаружил, что вложенный преобразователь не поддерживает сценарий, в котором поле находится в суперклассе, поэтому я улучшил его, чтобы искать поля до 10 уровней в иерархии наследования классов того класса, в который вы проецируете:

    public Object transformTuple(Object[] tuple, String[] aliases) {

        ...


                if (alias.contains(".")) {
                    nestedAliases.add(alias);

                    String[] sp = alias.split("\\.");
                    String fieldName = sp[0];
                    String aliasName = sp[1];

                    Class<?> subclass = getDeclaredFieldForClassOrSuperClasses(resultClass, fieldName, 1);
...
}

Где getDeclaredFieldForClassOrSuperClasses() определяется следующим образом:

private Class<?> getDeclaredFieldForClassOrSuperClasses(Class<?> resultClass, String fieldName, int level) throws NoSuchFieldException{
    Class<?> result = null;
    try {
        result = resultClass.getDeclaredField(fieldName).getType();
    } catch (NoSuchFieldException e) {
        if (level <= 10){
        return getDeclaredFieldForClassOrSuperClasses(
                resultClass.getSuperclass(), fieldName, level++);
        } else {
            throw e;
        }
    }
    return result;
}

Моя проекция Hibernate для этого вложенного свойства выглядела так:

Projections.projectionList().add( Property.forName("metadata.copyright").as("productMetadata.copyright"));

и класс, на который я проецирую, выглядит так:

public class ProductMetadata extends AbstractMetadata {
...
}

public abstract class AbstractMetadata {
...   
   protected String copyright;
...
}

Вместо создания Data Transfer Object (DTO)
В projectionlist внесите изменения ниже, и это будет работать для вас.

    ProjectionList projections = Projections.projectionList(); 
    projections.add(Projections.property("person.personID"), "personID");
    projections.add(Projections.property("person.wife"), "wife");
    projections.add(Projections.property("wife.name"));

    Criteria criteria = null; 
    criteria = getHandlerSession().createCriteria(Person.class,"person").createAlias("person.wife", "wife"); 
    criterion = Restrictions.eq("wife.age", 19);  
    criteria.add(criterion); 
    criteria.setProjection(projections);
    criteria.setResultTransformer(Transformers.aliasToBean(Person.class)); 
    return criteria.list();
Другие вопросы по тегам