Complex Hibernate Projections
Asked Answered
S

4

5

I want to ask, it is possible that I create query projections and criterion for more than one level deep? I have 2 model classes:

@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
     */       
}

My Criteria API :

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();

and I hope, I can query Person, with specified criteria for wife property, and specified return resultSet. so i used Projections for getting specified return resultSet

I want personID, name(Person), name(Wife) will returned. how API i must Use, i more prefer use Hibernate Criteria API.

This time, I used code above for getting my expected result, but it will throw Exception with error message : Exception in thread "main" org.hibernate.QueryException: could not resolve property: wife.name of: maladzan.model.Person, and whether my Restrictions.eq("wife.age", 19); is correct for getting person which has wife with 19 as her age value ?

Thanks

Succinctorium answered 24/8, 2012 at 8:31 Comment(0)
P
6

AFAIK it is not possible to project more than one level deep with aliastobean transformer. Your options are

  • create a flattened Data Transfer Object (DTO)
  • fill the resulting Person in memory yourself
  • implement your own resulttransformer (similar to option 2)

option 1 looks like this:

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();
Pushed answered 29/8, 2012 at 6:30 Comment(0)
B
5

I wrote the ResultTransformer, that does this exactly. It's name is AliasToBeanNestedResultTransformer, check it out on github.

Belle answered 11/9, 2013 at 1:5 Comment(4)
Hi Sami Andoni. I did used your AliasToBeanNestedResultTransformer for nested object creation, I do get nested object as nested object, but I've a small Issue. I intend to get only particular field in nested object, only few fields in parent object, but the outcome is all fields in parent object and all fields in nested object as nested object. I dont know whether your custom transformer is capable for fetching only a particular field, is it possible to fetch only a particular field in nested object as nested object?Markel
Have u solvd this issue?Equality
@JatinMalwal what issue?Belle
I'm trying to do the same thing to fetch only particular fields using AliasToBeanNestedResultTransformer. But AliasedTupleSubsetResultTransformer is not found when I'm try to make this.Equality
H
1

Thanks Sami Andoni. I was able to use your AliasToBeanNestedResultTransformer with a minor modification to suit my situation. What I found was that the nested transformer did not support the scenario where the field is in a super class so I enhanced it to look for fields up to 10 levels deep in the class inheritance hierarchy of the class you're projecting into:

    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);
...
}

Where getDeclaredFieldForClassOrSuperClasses() is defined as follows:

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;
}

My Hibernate projection for this nested property looked like this:

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

and the class I am projecting into looks like this:

public class ProductMetadata extends AbstractMetadata {
...
}

public abstract class AbstractMetadata {
...   
   protected String copyright;
...
}
Haircut answered 2/2, 2015 at 6:27 Comment(2)
Did you write a NestedTransformer which can support OneToMany (Collections) ?Markel
@Sangdol wrote it, I just enhanced it to support projecting into fields from a base class up to 10 levels deepHaircut
B
-1

Instead of creating Data Transfer Object (DTO)
In projectionlist make below changes and it will work for you.

    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();
Bituminous answered 23/9, 2015 at 7:54 Comment(1)
not working is different word, provide your hibernate version and full queryBituminous

© 2022 - 2024 — McMap. All rights reserved.