Member Menu
 
 Monthly JBoss newsletter:
 
Java Persistence with Hibernate
CaveatEmptor

Lightweight Class

Disclamer: While this pattern was and still is a common pattern in Hibernate world, there are alternative ways to solve this problem in Hibernate 3.

Suppose I have the following persistent class:

public class Document implements Node {
   private Long _key;
   private String _name;
   private Calendar _created;
   private Calendar _updated;
   private Folder _folder;
   private Clob _text;
   public String getKey() { return _key; }
   public void setKey(Long key) { _key = key; }
   public String getName() { return _name; }
   public void setName(String name) { _name = name; }
   public Calendar getCreated() { return _created; }
   public void setCreated(Calendar created) { _created = created; }
   public Calendar getUpdated() { return _updated; }
   public void setUpdated(Calendar updated) { _updated = updated; }
   public Folder getFolder() { return _folder; }
   public void setFolder(Folder folder) { _folder = folder; }
   public Clob getText() { return _text; }
   public void setText(Clob text) { _text = text; }
}

All properties of this class are mapped to columns of the DOCUMENTS table. For performance I don't want to open a stream to the underlying CLOB data every single time I retrieve a list of documents, rename a document, move a document, etc. Some commercial O/R mapping tools allow an object to be initially fetched with a default set of properties initialized. The remaining properties are fetched when they are first accessed. While Hibernate 3 has this feature, Hibernate 2 does not. However, there is a popular workaround that allows even better performance than that approach.

We break our Document class into a "lightweight" superclass and "heavyweight" subclass:

public class DocumentInfo implements Node {
   private Long _key;
   private String _name;
   private Calendar _created;
   private Calendar _updated;
   private Folder _folder;
   private Clob _text;
   public String getKey() { return _key; }
   public void setKey(Long key) { _key = key; }
   public String getName() { return _name; }
   public void setName(String name) { _name = name; }
   public Calendar getCreated() { return _created; }
   public void setCreated(Calendar created) { _created = created; }
   public Calendar getUpdated() { return _updated; }
   public void setUpdated(Calendar updated) { _updated = updated; }
   public Folder getFolder() { return _folder; }
   public void setFolder(Folder folder) { _folder = folder; }
}

public class Document extends DocumentInfo {
   private Clob _text;
   public Clob getText() { return _text; }
   public void setText(Clob text) { _text = text; }
}

We use the following mapping:

<class name="DocumentInfo" table="DOCUMENTS">
   <id name="key" type="long" column="ID">
       <generator class="native"/>
   </id>
   <property name="name"/>
   <property name="created"/>
   <property name="updated"/>
   <many-to-one name="folder"/>
</class>

<class name="Document" table="DOCUMENTS" polymorphism="explicit">
   <id name="key" type="long" column="ID">
       <generator class="native"/>
   </id>
   <property name="name"/>
   <property name="created"/>
   <property name="updated"/>
   <many-to-one name="folder"/>
   <property name="text"/>
</class>

Now the application may retrieve instances of DocumentInfo if it is interested in the info only, or instances of Document if it needs access to the text property. Because we have mapped Document with polymorphism="explicit", any queries like:

from DocumentInfo
from Node
from java.lang.Object

return instances of DocumentInfo. The following query returns only instances of Document:

from d in class Document

Caveat: in Hibernate 2, you cannot load the same database row as two different objects. As such, you cannot first load the DocumentInfo followed by the Document for the same Document ID as Hibernate will not upcast the object. For example, doing the following will cause a ClassCastException:

DocumentInfo info = (DocumentInfo) session.load(DocumentInfo.class, new Long(1));
Document doc = (Document) session.load(Document.class, new Long(1));

If you want to get the same Document row as a DocumentInfo and a Document, you will need to use session.evict() on DocumentInfo first - or you can add the following to your class mapping - polymorphism="explicit" this stops the Loader baulking at the two different classes being mapped to the same table and sharing the same primary key.

Note that arguably Hibernate does not really need lazy property fetching, since the query language supports projection, allowing the Java code to fetch exactly the needed properties.



  NEW COMMENT

Additional considerations 17 Aug 2004, 00:02 rkischuk
It's worth noting that if you have a DocumentBook class that has both a
Document attribute and a DocumentInfo attribute, you will need to take
several steps to avoid conflicts between these objects:

1. Make both relationships outer-join="false" (remember, both items
can't be loaded at the same time)

2. Make both referenced classes have a proxy, so they will be lazily
loaded.  proxy="Document" and proxy="DocumentInfo" will suffice. (same
reason as #1)


3. Make the table for Document different from the table for DocumentInfo
using case sensitivity (eg: DOCUMENTS and documents).  Otherwise, when
you load one (using a join fetch), Hibernate may attempt to load the
eagerly fetched entity into both the detail and summary fields.  If a
summary object is loaded into a detail field, class matching issues will
result.
 
Note on the light class pattern 07 Aug 2005, 04:04 jkares
Yeah this is a gret feature I do use with Hibernate 3 a lot from the
time I discovered it ... I really do recomend it to all of the Hibernate
users for large entitie like a person esp. for those running on a
multi-tier architecture. I use a base Person object for listings and an
PersonExt for detailed views. However still haven't tried it in the
following situ: I have a UniPerson object as an attribute of the User
class, this UniPerson is actually only an interface and the
implementations are Student etc, Student of couse is the light version
the table consists of attributes of my StudentExt class .. so I wonder
what happen if map it .. I hope and assume that when Hibernate loads my
UniPerson object for the User class it woudle be actually allways the
light version of the implementign class. I will test that soon and come
back ...
 
A serious limitation 10 Aug 2005, 02:03 floe
Using this pattern, when you need search the long text field, you're 
forced to return Document instance instead of DocumentInfo instance.
While the one-to-one pattern will allow you to return DocumentInfo 
instance.
Further more, split document table into document_info table and 
document_text table are more clear at dbms level.
 
Hibernae 3 related fetaure 22 Apr 2006, 03:24 afattahi
Hi,

Please mention what is the Hibernate 3 feature for this light wighted
pattern. We have reviewed “Improving Performance” chapter and “Fetching
Strategies” section for Hibernate 3, but did not find anything!

Regards,
Alireza Fattahi
 
The hibernate 3 technique 11 Oct 2007, 02:40 sandos
I believe the answer to how to do it in Hibernat 3 cam be found here:

http://www.javalobby.org/articles/hibernate-query-101/

Speficially, look under the "More advanced techniques" heading!

Good luck.
 
© Copyright 2006, Red Hat Middleware, LLC. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc. [Privacy Policy]