Search This Blog

Monday 10 December 2012

The Fetch Plan and the Fetch Strategy.

While studying HQL I came across the following statement:
"HQL and JPA QL ignore any fetching strategy defined in the mapping. But the global fetch plan is not ignored"
What is the strategy and what is the plan here ?
I went back in the books trying to understand the difference. The fetch plan indicates that when an object is loaded what else must be loaded. For e.g. when I load an entity:
  1. Do I need to have the associations loaded ?
  2. If there are any collections do they need to be loaded
  3. Are the properties to be loaded or they should be fetched only when needed.
These factors together form the fetch plan for the object.
Consider an Entity class which has one association and one Collection.
public class Entity {
    private String name;
    private Integer id;
    private Date date;
    private Master master;
    private Set<Child> children = new HashSet<Child>();
        //other code
}
Below is code to simply fetch an Entity record:
private static void testLoad() {
    final Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    System.out.println("Testing the loading");
    Entity entity = (Entity) session.get(Entity.class, 1);
    transaction.commit();
    session.close();
    System.out.println("Entity is " + entity);
}
The logs indicate the fired sql.
Testing the loading
Hibernate: 
    /* load com.model.Entity */ 
    select
        entity0_.ID as ID0_0_,
        entity0_.NAME as NAME0_0_,
        entity0_.DATE as DATE0_0_,
        entity0_.MASTER_ID as MASTER4_0_0_ 
    from
        ENTITY entity0_ 
    where
        entity0_.ID=?
Entity is [Entity] : ( id 1 , data : newOne , master.Id : 1 , date : 2010-07-04 
16:16:43.0 )]
As the plan involved lazy association, lazy collection and non-lazy properties only the non-association properties of Entity record was loaded. Thus the lazy attribute controls the fetch plan. The plan decides that at the end of the code how much data must have been loaded/ or what part of the object graph brought to life.If I were to change the plan to make the relations eager
<many-to-one name="master" class="Master" foreign-key="ENTITY_FK1" fetch="select" lazy="false">
    <column name="MASTER_ID"></column>
</many-to-one>
<set name="children" cascade="all-delete-orphan" inverse="true" fetch="select" lazy="false">
    <key column="ENTITY_ID" not-null="true" />
    <one-to-many class="Child" />
</set>
The output now shows a different set of SQL:
Testing the loading
Hibernate: 
    /* load com.model.Entity */ 
    select
        entity0_.ID as ID0_0_,
        entity0_.NAME as NAME0_0_,
        entity0_.DATE as DATE0_0_,
        entity0_.MASTER_ID as MASTER4_0_0_ 
    from
        ENTITY entity0_ 
    where
        entity0_.ID=?
Hibernate: 
    /* load com.model.Master */ 
    select
        master0_.ID as ID1_0_,
        master0_.DATA as DATA1_0_ 
    from
        ENTITY_MASTER master0_ 
    where
        master0_.ID=?
Hibernate: 
    /* load one-to-many com.model.Entity.children */ 
    select
        children0_.ENTITY_ID as ENTITY3_1_,
        children0_.ID as ID1_,
        children0_.ID as ID2_0_,
        children0_.`KEY` as KEY2_2_0_,
        children0_.ENTITY_ID as ENTITY3_2_0_ 
    from
        CHILD_ENTITY children0_ 
    where
        children0_.ENTITY_ID=?
Entity is [Entity] : ( id 1 , data : newOne , master.Id : 1 , date : 2010-07-04 
16:16:43.0 )]
Three select queries were now fired. As can be seen the Plan ensured that the object graph was completely initialised for the entity object.
So what is the fetch strategy then ? The strategy is the how for the plan. If the plan states that an association is to be treated lazy, the strategy tells how the association is to be fetched. Should it be via a separate query ( as in select strategy) or via a sub-select or through a join query. So the fetch parameter plays an important role here.
In the above case our fetch was set to select. Accordingly Hibernate used select queries to load the object associations.
When an entity is loaded via HQL the fetching strategy specified in the mapping files does not matter. To test the same, I set the fetch strategy for the collection as join.
The fetch plan was set to complete lazy again.
<set name="children" cascade="all-delete-orphan" inverse="true" fetch="join" >
    <key column="ENTITY_ID" not-null="true" />
    <one-to-many class="Child" />
</set>
The query is:
public static void testQueryFetch() {
    final Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    System.out.println("Testing the loading");
    Query q = session.createQuery("from Entity e where e.id = 1"); 
    Entity entity = (Entity) q.uniqueResult();
    transaction.commit();
    session.close();
    System.out.println("Entity is " + entity);
}
The logs indicate that no join query was created. The fetching strategy was ignored:
Testing the loading
Hibernate: 
    /* 
from
    Entity e 
where
    e.id = 1 */ 
    select
        entity0_.ID as ID0_,
        entity0_.NAME as NAME0_,
        entity0_.DATE as DATE0_,
        entity0_.MASTER_ID as MASTER4_0_ 
    from
        ENTITY entity0_ 
    where
        entity0_.ID=1
Entity is [Entity] : ( id 1 , data : newOne , master.Id : 1 , date : 2010-07-04 
16:16:43.0 )]
To this mix, I added the fetch plan with collection as non-lazy (or eager). The fetch mode was retained as join. The above code's output changes:
Testing the loading
Hibernate: 
    /* 
from
    Entity e 
where
    e.id = 1 */ 
    select
        entity0_.ID as ID0_,
        entity0_.NAME as NAME0_,
        entity0_.DATE as DATE0_,
        entity0_.MASTER_ID as MASTER4_0_ 
    from
        ENTITY entity0_ 
    where
        entity0_.ID=1
Hibernate: 
    /* load one-to-many com.model.Entity.children */ 
    select
        children0_.ENTITY_ID as ENTITY3_1_,
        children0_.ID as ID1_,
        children0_.ID as ID2_0_,
        children0_.`KEY` as KEY2_2_0_,
        children0_.ENTITY_ID as ENTITY3_2_0_ 
    from
        CHILD_ENTITY children0_ 
    where
        children0_.ENTITY_ID=?
Entity is [Entity] : ( id 1 , data : newOne , master.Id : 1 , date : 2010-07-04 
16:16:43.0 )]
Hibernate applied the select strategy here. For every entity it fired a different query to fetch children i.e. fetch = select.
While Hibernate ignored by fetching strategy(join) it still loaded the collection( eager). Thus HQL respects the fetch plan , but ignores the fetch strategy. The funny thing is when I specified the batching strategy as subselect, HQL obeyed and loaded all collections when I tried to access the first record. It also works fine for batch-size. It only failed to work with join fetching strategy.
The thing is even if you specify a fetch strategy of join, HQL will not execute the join unless you have specified join in the query. If it worked differently it would mean HQL is not doing what the query said it to.

1 comment:

  1. Great work. I was really confused after reading this line in book Java persistence with Hibernate. Again thanks, this blog cleared my doubts.

    ReplyDelete