Hibernate uses proxies to ensure that the data is fetched only on a need basis. So if we have a Shelf class that has a set of books, the loading of a shelf object will not load the Book set. Instead a collection proxy will be assigned to the field. It is only when we access the methods of the collection, that the actual fetch occurs.
The set is loaded only when we call the getAllBooks() method. (click on the image to enlarge it)
Hibernate provides an additional lazy setting for collections - lazy="extra". The collection is now a smarter collection. It does not fetch all records for a size method call.
Similarly the contains method also did not result in any data loading.
The same rules would apply for a Map (containsKey and get method) and List (get method)
This is a good option to use with very large collections.
public class Book { private String name; private Integer id; private Shelf shelf; //setter -getter methods }And the shelf class is as below:
public class Shelf { private Integer id; private String code; private Set<Book> allBooks = new HashSet<Book>(); //setter -getter methods }The hbms for the two would be as below:
Book.hbm.xml:
<?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="Book" table="BOOK"> <id name="id" type="integer"> <column name="ID" /> <generator class="native" /> </id> <property name="name" type="string"> <column name="Name" length="50" not-null="true" /> </property> <many-to-one name="shelf" class="Shelf" foreign-key="BOOK_FK1"> <column name="shelf_id"></column> </many-to-one> </class> </hibernate-mapping>
Shelf.hbm.xml
<?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="save-update" inverse="true"> <key column="SHELF_ID" not-null="true" /> <one-to-many class="Book" /> </set> </class> </hibernate-mapping>I wrote the below code to simply load a Book object.
public static void testLoad() { Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); Shelf shelf = (Shelf) session.load(Shelf.class, shelfId); System.out.println("Number of books on " + shelf.getCode() + " is " + shelf.getAllBooks().size()); transaction.commit(); session.close(); }If we were to execute the code, we can see that our HashSet class has been replaced with a run time generated class:
The set is loaded only when we call the getAllBooks() method. (click on the image to enlarge it)
/* 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=? Number of books on SH001 is 2There could be scenarios wherein you simply need to test the size of the set or check if a particular book is present on the set. In such cases the records will be fetched from the books table, although we do not need them as we are not performing any operations on the set.
Hibernate provides an additional lazy setting for collections - lazy="extra". The collection is now a smarter collection. It does not fetch all records for a size method call.
<set name="allBooks" cascade="save-update" inverse="true" lazy="extra"> <key column="SHELF_ID" not-null="true" /> <one-to-many class="Book" /> </set>Consider the below code
public static void testSmartLoad() { Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); Shelf shelf = (Shelf) session.load(Shelf.class, shelfId); Set<Book> allBooks = shelf.getAllBooks(); System.out.println("Are books there on " + shelf.getCode() + " ? " + !allBooks.isEmpty()); System.out.println("Number of books on " + shelf.getCode() + " is " + allBooks.size()); Book searchBook = new Book(); searchBook.setId(4); System.out.println("Is book there ? " + allBooks.contains(searchBook)); transaction.commit(); session.close(); }The logs indicate the fired SQL queries:
Hibernate: /* load com.collection.smart.Shelf */
select shelf0_.ID as ID1_0_, shelf0_.CODE as CODE1_0_ from SHELF shelf0_ where shelf0_.ID=? Hibernate: select count(ID) from BOOK where SHELF_ID =? Are books there on SH001 ? true Number of books on SH001 is 2 Hibernate: select 1 from BOOK where SHELF_ID =? and ID =? Is book there ? falseAs can be seen the isEmpty/ size method both did not result in any load calls. instead simple select calls were executed to get the size of the collection.
Similarly the contains method also did not result in any data loading.
The same rules would apply for a Map (containsKey and get method) and List (get method)
This is a good option to use with very large collections.
This is a nice feature, thanks for posting this.
ReplyDeleteThanks for your effort.
ReplyDeleteyes, great example
ReplyDeletenow i can do it by myself
thanks for sharing mate
Thanks Robin.
ReplyDeleteNow i got the concept
But didn't work if you are using HQL to fetch All records
ReplyDeletenice article..thanks for posting it..
ReplyDelete