Search This Blog

Saturday 10 September 2011

Dealing with common content in mapping files

In the previous post we looked into component mapping. I created a component for audit fields and added it to my Java POJO class. Everything worked fine, until I observed that the same mapping is done in the scores of other database tables that we create in our application. The same fields are repeatedly added in our java models. The same mappings are created in our hbm files. The process gets repeated continuously.
Also the amount of repeated (and in the case of audit trails, the not so main) content gets repeated across mapping documents reducing the readability. While the java code can be made easily more readable either via inheritance or via composition (using components), Hibernate provides a nifty trick that reduces the XML content in our file and making it more readable and concise.
The XML mapping file for user in our previous example is as below. The common content that will be shared with other hibernate-mapping elements is highlighted:
<?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.model">
    <class name="User" table="USER">
        <id name="id" type="long">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>

        <component name="auditField" class="com.model.component.AuditField">
            <property name="createdBy" type="integer">
                <column name="Created_By" />
            </property>
            <property name="createdDate" type="timestamp">
                <column name="Created_Date" length="19" />
            </property>
            <property name="modifiedBy" type="integer">
                <column name="Modified_By" />
            </property>
            <property name="modifiedDate" type="timestamp">
                <column name="Modified_Date" length="19" />
            </property>
        </component>
    </class>
</hibernate-mapping>
Rather than repeat this in other documents we can use a concept called custom xml entities that allows us to move this content to a common location.
Our modified (and much reduced) xml document is as below:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
[
<!ENTITY auditDetails SYSTEM "classpath://com/model/AuditDetails.hbm.xml">
]>
<hibernate-mapping package="com.model">
    <class name="User" table="USER">
        <id name="id" type="long">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>
        
        <component name="auditField" class="com.model.component.AuditField">
            &auditDetails;
        </component>
    </class>
</hibernate-mapping>
As can be seen, the audit related mapping has been replaced by "&auditDetails" or what is called an entity placeholder. Its actual value has been defined at the top of the file as a part of the DTD.
[
<!ENTITY auditDetails SYSTEM "classpath://com/model/AuditDetails.hbm.xml">
]>

The XML parser will substitute the value of the placeholder with the contents of the file AuditDetails.hbm.xml at application start up. The contents of the file is simply the common mapping code:
<property name="createdBy" type="integer">
    <column name="Created_By" />
</property>
<property name="createdDate" type="timestamp">
    <column name="Created_Date" length="19" />
</property>
<property name="modifiedBy" type="integer">
    <column name="Modified_By" />
</property>
<property name="modifiedDate" type="timestamp">
    <column name="Modified_Date" length="19" />
</property>
Recently I tried using the technique in a STS UI for a project and the editor kept screaming for errors. So I gave up on the same and reverted to defining common content all over the place :(
Yet to fix that issue.

3 comments:

  1. With refference to mentioned artical,
    Is it required to add AuditDetails.hbm.xml entry into hibernate.hbm.xml ?

    ReplyDelete
  2. No You do need to add the AuditDetails.hbm.xml in the hibernate configuration file. In fact you do not even need to name it with ".hbm.xml" extension. The file is detected on the basis of the below text
    SYSTEM "classpath://com/model/AuditDetails.hbm.xml"
    You can call it AuditDetails.fragment for that matter.

    ReplyDelete
  3. Hi, please have you tried if this mechanism can be implemented in reverse engineering. This is to avoid the tediousness attached to copying this content in a huge application having almost a thousand tables

    ReplyDelete