Search This Blog

Sunday, 5 August 2012

fetch = join for Collection

There could be a scenario wherein we need an association to be always available whenever an Entity is loaded. In this case the additional select queries that are fired are a performance loss. We could tell Hibernate to fetch the entity and the association in a single call to the database.
Consider the case where we need all the Shelf and its books to be always available. One way to do this would be to change our fetch strategy for the  Book set in Shelf to join.
<?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">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="code" type="string">
            <column name="CODE" length="50" not-null="true" />
        </property>        
        <set name="allBooks" cascade="all-delete-orphan" inverse="true" 
            fetch="join" >
            <key column="SHELF_ID" not-null="true" />
            <one-to-many class="Book" />
        </set>

    </class>
</hibernate-mapping>
If were to load a specific shelf as in the below code:
public static void testJoinedLoad() {
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    Shelf shelf = (Shelf) session.load(Shelf.class, 1); 
    System.out.println("shelf class is " + shelf.getClass() 
             + " and set class is " + shelf.getAllBooks().getClass());        
    System.out.println("shelf is " + shelf.getCode() + " and book count is  " 
+ shelf.getAllBooks().size());
    transaction.commit();
    session.close();
}
The SQL logs are as below:
Hibernate: 
    /* load com.collection.smart.Shelf */ 
    select
        shelf0_.ID as ID1_1_,
        shelf0_.CODE as CODE1_1_,
        allbooks1_.SHELF_ID as SHELF3_3_,
        allbooks1_.ID as ID3_,
        allbooks1_.ID as ID0_0_,
        allbooks1_.Name as Name0_0_,
        allbooks1_.shelf_id as shelf3_0_0_ 
    from
        SHELF shelf0_ 
    left outer join
        BOOK allbooks1_ 
            on shelf0_.ID=allbooks1_.SHELF_ID 
    where
        shelf0_.ID=? 
shelf class is class com.collection.smart.Shelf$$EnhancerByCGLIB$$c94b6717 and s
et class is class org.hibernate.collection.PersistentSet  
shelf is SH001 and book count is  2
As seen Hibernate loads the shelf and all the books on it.
I executed a list query to fetch multiple Shelves.
public static void testJoinedLoad() {
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    System.out.println("Testing Query select");
    session = sessionFactory.openSession();
    transaction = session.beginTransaction();
    @SuppressWarnings("unchecked")
    List<Shelf> shelfs = session.createCriteria(Shelf.class)
           .add(Restrictions.le("id", 3)).list();
    for (Shelf shelf : shelfs) {
        System.out.println("shelf class is " + shelf.getClass());
        Set<Book> books = shelf.getAllBooks();            
        System.out.println("The Collection class is " + shelf.getAllBooks().getClass());
        System.out.println("Total books is " + books.size());
    }
    
    transaction.commit();
    session.close();
}
The SQL logs are as below:
Testing Query select
Hibernate: 
    /* criteria query */ 
select
        this_.ID as ID1_1_,
        this_.CODE as CODE1_1_,
        allbooks2_.SHELF_ID as SHELF3_3_,
        allbooks2_.ID as ID3_,
        allbooks2_.ID as ID0_0_,
        allbooks2_.Name as Name0_0_,
        allbooks2_.shelf_id as shelf3_0_0_ 
    from
        SHELF this_ 
    left outer join
        BOOK allbooks2_ 
            on this_.ID=allbooks2_.SHELF_ID 
    where
        this_.ID<=?
shelf class is class com.collection.smart.Shelf
The Collection class is class org.hibernate.collection.PersistentSet
Total books is 2
shelf class is class com.collection.smart.Shelf
The Collection class is class org.hibernate.collection.PersistentSet
Total books is 2
shelf class is class com.collection.smart.Shelf
The Collection class is class org.hibernate.collection.PersistentSet
Total books is 1
shelf class is class com.collection.smart.Shelf
The Collection class is class org.hibernate.collection.PersistentSet
Total books is 1
As can be seen the entire result set was fetched in a single query for the criteria. I then executed the same query using an equivalent HQL base code (like the previous example):
public static void testJoinedLoad() {
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    System.out.println("Testing Query select");
    session = sessionFactory.openSession();
    transaction = session.beginTransaction();
    @SuppressWarnings("unchecked")
    List<Shelf> shelfs = session.createQuery("from Shelf shelf where shelf.id <= 3 ")
               .list();
    for (Shelf shelf : shelfs) {
        System.out.println("shelf class is " + shelf.getClass());
        Set<Book> books = shelf.getAllBooks();            
        System.out.println("The Collection class is " + shelf.getAllBooks().getClass());
        System.out.println("Total books is " + books.size());
    }
    
    transaction.commit();
    session.close();
}
The SQL logs are as below:
Testing Query select
Hibernate: 
    /* 
from
    Shelf shelf 
where
    shelf.id <= 3  */ 
    select
        shelf0_.ID as ID1_,
        shelf0_.CODE as CODE1_ 
    from
        SHELF shelf0_ 
    where
        shelf0_.ID<=3
shelf class is class com.collection.smart.Shelf
The Collection class is class org.hibernate.collection.PersistentSet
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=?
Total books is 2
shelf class is class com.collection.smart.Shelf
The Collection class is class org.hibernate.collection.PersistentSet
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=?
Total books is 1
shelf class is class com.collection.smart.Shelf
The Collection class is class org.hibernate.collection.PersistentSet
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=?
Total books is 1
In this case the join strategy has not been used. The behaviour is same as that seen in a fetch=select strategy. This is because HQL does not respect the join fetching strategy specified in the mappings unlike the Criteria code.

No comments:

Post a Comment