Search This Blog

Friday, 16 September 2011

Creating a Hibernate Custom Type - 3

In the previous articles we saw how to create Custom Mapping Types - both implementations of UserType and CompositeUserType. There is the possibility wherein we may need to add some configuration values to our custom types.
This would provide us with the ability to use the mapping type with certain variations in different scenarios.
In case of AuditData class, one scenario would be to control the format of time stamp values saved. In certain tables we may not want to persist with the time component and in other tables we would like to preserve the audit trail as a time-stamp entry.(very twisted I agree, but this is the best I can come up with right now ;-{  )
For these kind of configuration settings, Hibernate has provided the ParameterizedType interface that can be used in combination with the other mapping interfaces. I improved upon our previous mapping type to provide this configuration ability of saving time component.
The new mapping type is as below:
package com.customtype;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.ParameterizedType;

import com.model.component.AuditData;

public class ParameterisedCompositeAuditType extends CompositeAuditType
        implements CompositeUserType, ParameterizedType {

    private static final String SAVE_TIME_PROPERTY_KEY = "saveTimeComponent";
    public boolean saveTimeAlso;

    private Timestamp normalizeDate(Date date) {
        Timestamp dateResult = null;
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        if (!saveTimeAlso) {
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            long time = cal.getTimeInMillis();
            dateResult = new Timestamp(time);
        } else {
            dateResult = new Timestamp(date.getTime());
        }

        return dateResult;
    }

    @Override
    public void setParameterValues(Properties parameters) {
        String booleanValue = parameters.getProperty(SAVE_TIME_PROPERTY_KEY);
        Boolean saveTimeSetting = Boolean.valueOf(booleanValue);
        this.saveTimeAlso = saveTimeSetting.booleanValue();
    }

    /**
     * Before executing the save call this method is called. It will set the
     * values in the prepared statement
     */
    @Override
    public void nullSafeSet(final PreparedStatement preparedStatement,
            final Object value, final int property,
            final SessionImplementor sessionImplementor)
            throws HibernateException, SQLException {
        if (null == value) {
            preparedStatement.setNull(property, Hibernate.INTEGER.sqlType());
            preparedStatement.setNull(property + 1, Hibernate.TIMESTAMP.sqlType());
            preparedStatement.setNull(property + 2, Hibernate.INTEGER.sqlType());
            preparedStatement.setNull(property + 3, Hibernate.TIMESTAMP.sqlType());
        } else {
            final AuditData auditData = (AuditData) value;
            preparedStatement.setInt(property, auditData.getCreatedBy());
            preparedStatement.setTimestamp(property + 1,
                    this.normalizeDate(auditData.getCreatedDate()));
            preparedStatement.setInt(property + 2, auditData.getModifiedBy());
            preparedStatement.setTimestamp(property + 3,
                    this.normalizeDate(auditData.getModifiedDate()));
        }
    }

}
The above class extends from the CompositeAuditType class of our previous example with the save method being overridden to implement the selective saving of time component in modified date and created date fields.
The only method introduced by the other interface is the setParameterValues() method that takes a properties instance as its parameter. This includes all the configuration settings available with this instance of custom mapping. The parameters are set in the mapping document or via XML annotations.
<?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="TypedUser" table="USER">
        <id name="id" type="long">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>
        <property name = "auditData">
            <column name="Created_By" />
            <column name="Created_Date" length="19" />
            <column name="Modified_By" />
            <column name="Modified_Date" length="19" />
            <type name="com.customtype.ParameterisedCompositeAuditType">
                <param name="saveTimeComponent">false</param>
            </type>
        </property>
    </class>
</hibernate-mapping>
In the previous types, we specified the type as an attribute of the property element. Here it is sub-element of the property element. 
I executed a call to create records with the saveTimeComponent set to true and false.
static void testCreate() {
    Session session = SESSION_FACTORY.openSession();
    Transaction transaction = null;
    try {
        final TypedUser user = new TypedUser();
        user.setName("TimeFormatted");
        final AuditData auditData = new AuditData();
        auditData.setCreatedBy(8);
        auditData.setCreatedDate(new Date());
        auditData.setModifiedBy(2);
        auditData.setModifiedDate(new Date());
        user.setAuditData(auditData);
        transaction = session.beginTransaction();
        session.save(user);
        transaction.commit();
        System.out.println("Name : " + user.getName() + " Audit details : " 
                + user.getAuditData() );
    } catch (HibernateException e) {
        e.printStackTrace();
        transaction.rollback();
    } finally {
        session.close();
    }
}
The two records accordingly were saved based on the value of the configuration
Records saved using the ParameterizedType
As can be seen, the record with id 2 was saved without the time component (saveTimeComponent = false) while the record with id 3 was saved with the time stamp value(saveTimeComponent = true).

No comments:

Post a Comment