Search This Blog

Tuesday, 3 April 2012

Lazy Behavior in Hibernate

Hibernate has an attribute called lazy. This makes an appearance in various parts of the hbm file. The first point is the class element. (To try these properties, I am using the same Book- Shelf example from an earlier post.)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.collection.smart">
    <class name="Shelf" table="SHELF" lazy ="false">
The class element includes the lazy attribute that has a default value "true". If we set this value to false then proxy generation will be disabled for the Shelf class. Any call to load will result in a database hit.
public static void testEagerLoad() {
    System.out.println("Non Lazy loading the entities");
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    Shelf shelf = (Shelf) session.load(Shelf.class, shelfId);
    System.out.println(shelf.getClass());
    transaction.commit();
    session.close();
}
The logs indicate the absence of the proxy.
Non Lazy loading the entities
Hibernate: 
    /* load com.collection.smart.Shelf */  
    select
        shelf0_.ID as ID1_0_,
        shelf0_.CODE as CODE1_0_ 
    from
        SHELF shelf0_ 
    where
        shelf0_.ID=?
class com.collection.smart.Shelf
The behavior is also different in the association. Consider the below code:
public static void testEagerAssociationLoad() {
    System.out.println("Non Lazy loading the association");
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    Book book1 = (Book) session.load(Book.class, book1Id);
    System.out.println("name is " + book1.getName());
    System.out.println(book1.getClass());
    System.out.println(book1.getShelf().getClass());
    transaction.commit();
    session.close();
}
If we were to run the code with lazy = true for shelf class the output would be as below:
Non Lazy loading the association
Hibernate: 
    /* load com.collection.smart.Book */  
    select
        book0_.ID as ID0_0_,
        book0_.Name as Name0_0_,
        book0_.shelf_id as shelf3_0_0_ 
    from
        BOOK book0_ 
    where
        book0_.ID=?
name is Way Oh way
class com.collection.smart.Book$$EnhancerByCGLIB$$4a555168
class com.collection.smart.Shelf$$EnhancerByCGLIB$$28cb8759
If run with proxy loading disabled for shelf class then the logs are different:
Non Lazy loading the association
Hibernate: 
    /* load com.collection.smart.Book */ 
    select
        book0_.ID as ID0_1_,
        book0_.Name as Name0_1_,
        book0_.shelf_id as shelf3_0_1_,
        shelf1_.ID as ID1_0_,
        shelf1_.CODE as CODE1_0_ 
    from
        BOOK book0_ 
    left outer join
        SHELF shelf1_ 
            on book0_.shelf_id=shelf1_.ID 
    where
        book0_.ID=?
name is Way Oh way
class com.collection.smart.Book$$EnhancerByCGLIB$$4a555168
class com.collection.smart.Shelf
As seen above if lazy is false, Hibernate eagerly fetched the shelf association. If lazy were true then the association isn't loaded. A proxy is used and a separate SQL query is fired to load the shelf class.
The idea of disabling proxies is a very bad idea in Hibernate. It results in a lot of data being fetched from the database irrespective of the need for it.
The second point of using the lazy attribute is in associations or collections.
<many-to-one name="shelf" class="Shelf" foreign-key="BOOK_FK1" lazy ="false">
    <column name="shelf_id"></column>
</many-to-one>
This is a part of the Book.hbm.xml file. It tells Hibernate that whenever a Book object is loaded, the related shelf property must be fetched too. If I call the testEagerAssociationLoad method, the logs indicate the SQL output:
Non Lazy loading the association
Hibernate: 
    /* load com.collection.smart.Book */  
    select
        book0_.ID as ID0_0_,
        book0_.Name as Name0_0_,
        book0_.shelf_id as shelf3_0_0_ 
    from
        BOOK book0_ 
    where
        book0_.ID=?
Hibernate: 
    /* load com.collection.smart.Shelf */ 
    select
        shelf0_.ID as ID1_0_,
        shelf0_.CODE as CODE1_0_ 
    from
        SHELF shelf0_ 
    where
        shelf0_.ID=?
name is Way Oh way
class com.collection.smart.Book$$EnhancerByCGLIB$$4a555168
class com.collection.smart.Shelf
As seen above multiple select queries got executed when Book's getName() method was called. The Shelf class is also not represented by a proxy here.
The same attribute is also available with collections element. I made similar change in the fragment in Shelf.hbm.xml referring to allBooks set.
<set name="allBooks" cascade="save-update" inverse="true" lazy ="false">
    <key column="SHELF_ID" not-null="true" />
    <one-to-many class="Book" />
</set>
If I were to simply load a shelf record from the database
public static void  testEagerCollectionLoad() {
    System.out.println("Non Lazy loading the collection");
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    Shelf shelf = (Shelf) session.load(Shelf.class, shelfId);
    System.out.println("shelf code is " + shelf.getCode());
    transaction.commit();
    session.close();
}
The output indicates the generated SQL queries
Non Lazy loading the collection
Hibernate: 
    /* load com.collection.smart.Shelf */ 
    select
        shelf0_.ID as ID1_0_,
        shelf0_.CODE as CODE1_0_ 
    from
        SHELF shelf0_ 
    where
        shelf0_.ID=?
Hibernate: 
    /* load one-to-many com.collection.smart.Shelf.allBooks */ 
    select
        allbooks0_.SHELF_ID as SHELF3_1_,
        allbooks0_.ID as ID1_,
        allbooks0_.ID as ID0_0_,
        allbooks0_.Name as Name0_0_,
        allbooks0_.shelf_id as shelf3_0_0_ 
    from
        BOOK allbooks0_ 
    where
        allbooks0_.SHELF_ID=?
shelf code is SH001
As can be seen an additional query was fired to load all the Books for the given Shelf too.
The last part of the object that can be lazily loaded is a property or a component. This is achieved by the following setting in the hbm file.
<property name="code" type="string" lazy ="false">
    <column name="CODE" length="50" not-null="true" />
</property>
To get this to work would require Hibernate to perform byte code instrumentation. This is done by Hibernate during compile time. An ant task for the same is available here. It is very rare to have the need for this level of lazy loading. I managed to pull it off in this post.
Thus for lazy loading of properties we need byte-code instrumentation. However this rule does not apply to all Hibernate types. Two types - blob and clob have been exempted from the above rule.
Consider the below class and hbm
import java.sql.Clob;

public class SimpleClob {
    private Integer id;
    private String name;    
    private Clob clobData;
        //setter -getters
}
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.collection.smart">
    <class name="SimpleClob" table="SIMPLE_CLOB">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="clobData" type="clob" lazy="true">
            <column name="clob_data" not-null="true" />
        </property>
        <property name="name" type="string">
            <column name="Name" length="50" not-null="true" />
        </property>
    </class>
</hibernate-mapping>
In the above class I have included a clob property. Hibernate treats these properties as lazy by default. There is no need to even specify the lazy attribute here.
How it works is when we load a clob object from the database, the field value of clobData is not loaded. Instead a kind of pointer to the data is fetched. When we actually access this value using the getClobData() method, the values is fetched from the database using a second select query. I tried the same using the below code:
session = sessionFactory.openSession();
SimpleClob clob= (SimpleClob) session.load(SimpleClob.class, 1);
System.out.println("Clob data is " + clob.getClobData());
The output logs are as below:
Hibernate: 
    /* load com.collection.smart.SimpleClob */  
    select
        simpleclob0_.ID as ID2_0_,
        simpleclob0_.clob_data as clob2_2_0_,
        simpleclob0_.Name as Name2_0_ 
    from
        SIMPLE_CLOB simpleclob0_ 
    where
        simpleclob0_.ID=?
However this did not work as expected. All the properties including clob was loaded in a single SQL query itself.Google-ing for the solution brought me to this link that suggest that this is a problem of my MySQL jdbc driver and that this functionality is only implemented correctly in PostgreSQL.

4 comments:

  1. Hey...Your posts are so interesting and informative. I appreciate your effort and time towards these posts and great thanks for the all the concepts your throwing.... :-)

    Another software engg. [ Read your profile]

    ReplyDelete
  2. You Said for CLOB default behavior is Lazy Loading ... but i guess Byte Code instrumentation would be needed , right ?

    ReplyDelete
    Replies
    1. Hi,
      No, for LOBs the Database driver must actually return just a pointer and not the whole data. However this behavior is implemented correctly only in the PostgreSQL.

      Instrumentation is to be used when you want other properties such as an integer/char etc to be loaded lazily. As the LOB behavior is not available by default with most databases, instrumentation is the only option to get it to work lazily

      Delete