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