Lightweight Class

Version 1

    {{{test}}}

    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.