Search This Blog

Friday 28 June 2013

Result Transformers

In the previous post we saw the need for ResultTransformer, how using the value of Criteria.DISTINCT_ROOT_ENTITY filters duplicate records.
The default value for the result transformer is Criteria.ROOT_ENTITY. Adding the below line in the code ensures that the result will only load the root entities.
criteria.setResultTransformer(Criteria.ROOT_ENTITY);
We also have the option of loading and getting the records in a Map format.
public static void testSimpleFetchUsingMapTransformer() {
    final Session session = sessionFactory.openSession();
    Criteria criteria = session.createCriteria(Entity.class);
    criteria.createAlias("children", "c");
    criteria.add(Restrictions.le("id", 6));
    criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
    int i = 1;
    List<Map<String, Object>> objects = criteria.list();
    for (Map<String, Object> record : objects) {
        Entity entity = (Entity) record.get(Criteria.ROOT_ALIAS);
        Child child = (Child) record.get("c");
        System.out.println((i++) + " : " + entity + " - " + child);
    }
}
When the default result transformer is used, Hibernate returned duplicate Entity instances. Using the Criteria.ALIAS_TO_ENTITY_MAP the result is returned as a list of map instances, with each map holding the details of the row.
Iteration over the map gives us the entities we loaded. In this case duplicates are returned.
The Query is :
select
    this_.ID as ID0_1_,
    this_.NAME as NAME0_1_,
    this_.DATE as DATE0_1_,
    this_.MASTER_ID as MASTER4_0_1_,
    c1_.ID as ID2_0_,
    c1_.`KEY` as KEY2_2_0_,
    c1_.ENTITY_ID as ENTITY3_2_0_ 
from
    ENTITY this_ 
inner join
    CHILD_ENTITY c1_ 
        on this_.ID=c1_.ENTITY_ID 
where
    this_.ID<=?
and the output:
1 : [Entity] : ( id 2 , data : entity1 , master.Id : 1 , date : null )] - [Child
] : ( id 1 , key : 1001 , parent.Id : 2 )]
2 : [Entity] : ( id 2 , data : entity1 , master.Id : 1 , date : null )] - [Child
] : ( id 3 , key : 1003 , parent.Id : 2 )]
3 : [Entity] : ( id 2 , data : entity1 , master.Id : 1 , date : null )] - [Child
] : ( id 4 , key : 1004 , parent.Id : 2 )]
4 : [Entity] : ( id 3 , data : entity2 , master.Id : 1 , date : null )] - [Child
] : ( id 2 , key : 1002 , parent.Id : 3 )]
5 : [Entity] : ( id 4 , data : entity100 , master.Id : 2 , date : null )] - [Chi
ld] : ( id 5 , key : 4 , parent.Id : 4 )]
6 : [Entity] : ( id 5 , data : entity102 , master.Id : 2 , date : null )] - [Chi
ld] : ( id 6 , key : 43 , parent.Id : 5 )]
The Entities are fetched from the map using the alias names specified. The root Entity uses the alias "this" (If we look at the query, the SQL aliases simply have an underscore character added to them
/**
 * The alias that refers to the "root" entity of the criteria query.
 */
public static final String ROOT_ALIAS = "this";
) We saw how HQL allows us to use new keyword and get the results of the query returned as records of a class.
public static void testUsingBeanTransformer() {
    final Session session = sessionFactory.openSession();

    Criteria criteria = session.createCriteria(Entity.class);
    criteria.createAlias("master", "m");
    criteria.setProjection(Projections.projectionList()
            .add(Property.forName("name"))
            .add(Property.forName("m.data")));

    criteria.setResultTransformer(new AliasToBeanConstructorResultTransformer(
            EntityParent.class.getConstructors()[0]));

    List<EntityParent> objects = criteria.list();
    for (EntityParent pojo : objects) {
        System.out.println("Entity name: " + pojo.getEntityName()
                + " ,Master Name : " + pojo.getMasterName());
    }
}
In this case we used a AliasToBeanConstructorResultTransformer.The constructor for EntityParent is :
public EntityParent(String entityName, String masterName) {
    super();
    this.entityName = entityName;
    this.masterName = masterName;
}
Based on data returned by the Query, Hibernate calls the constructor (in our case we have only one) and creates a record each returning a list of EntityParent objects. The output of the above method is :
select
    this_.NAME as y0_,
    m1_.DATA as y1_ 
from
    ENTITY this_ 
inner join
    ENTITY_MASTER m1_ 
on this_.MASTER_ID=m1_.ID
Entity name: entity1 ,Master Name : master No 1
Entity name: entity2 ,Master Name : master No 1
Entity name: entity100 ,Master Name : master No 2
Entity name: entity102 ,Master Name : master No 2
A similar result can be obtained using anAliasToBeanResultTransformer.
public static void testUsingBeanTransformer2() {
    final Session session = sessionFactory.openSession();

    Criteria criteria = session.createCriteria(Entity.class);
    criteria.createAlias("master", "m");
    criteria.setProjection(Projections.projectionList()
            .add(Property.forName("name").as("entityName"))
            .add(Property.forName("m.data").as("masterName")));

    criteria.setResultTransformer(new AliasToBeanResultTransformer(
            EntityParent.class));
    List<EntityParent> objects = criteria.list();
    for (EntityParent pojo : objects) {
        System.out.println("Entity name: " + pojo.getEntityName()
                + " ,Master Name : " + pojo.getMasterName());
    }
}
For the above code to work I had to add a default constructor. In the above code, Hibernate creates an instance of EntityParent for each record returned. It then sets the values of the objects by calling setter methods. The setter methods are identified by the alias assigned to the variables in the project list. The generated output is same as the previous method.
select
    this_.NAME as y0_,
    m1_.DATA as y1_ 
from
    ENTITY this_ 
inner join
    ENTITY_MASTER m1_ 
on this_.MASTER_ID=m1_.ID
Entity name: entity1 ,Master Name : master No 1
Entity name: entity2 ,Master Name : master No 1
Entity name: entity100 ,Master Name : master No 2
Entity name: entity102 ,Master Name : master No 2
Interesting thing is that the aliases didn't appear in this SQL clause.

No comments:

Post a Comment