Using custom data type in Hibernate

Posted by {"name"=>"Palash Ray", "email"=>"paawak@gmail.com", "url"=>"https://www.linkedin.com/in/palash-ray/"} on October 31, 2010 · 4 mins read

Why need custom data type?

I have a java.util.Calendar object which I want to persist in the db, along with the TimeZone. Suppose I am working with a db which does not have support for storing TimeZone. How do I proceed? One of the easiest solutions would be to store the entire timestamp as a VARCHAR. Granted, but how do I instruct Hibernate to use a java.util.Calendar object instead of a String? Its done by annotating with @Type(type=""), where type should be the fully qualified name of a class implementing the org.hibernate.usertype.UserType interface.

Custom UserType

public class TimeWithZone implements UserType {
    private static final Logger LOG = Logger.getLogger(TimeWithZone.class);
    /**
     * Define the supported column types
     */
    @Override
    public int[] sqlTypes() {
        return new int[] { Types.VARCHAR };
    }
    @Override
    public Class returnedClass() {
        return Calendar.class;
    }
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == null || y == null) {
            return false;
        }
        return x.equals(y);
    }
    @Override
    public int hashCode(Object x) throws HibernateException {
        if (x != null) {
            return x.hashCode();
        }
        return 0;
    }
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
            throws HibernateException, SQLException {
        Calendar cal = null;
        String timestampStr = rs.getString(names[0]);
        if (timestampStr != null) {
            cal = getTimeWithZone(timestampStr);
        }
        return cal;
    }
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.VARCHAR);
        } else {
            doInstanceCheck(value);
            Calendar cal = (Calendar) value;
            st.setString(index, getTimeWithZone(cal));
        }
    }
    @Override
    public Object deepCopy(Object value) throws HibernateException {
        Calendar clone = null;
        if (value != null) {
            doInstanceCheck(value);
            Calendar cal = (Calendar) value;
            // just copying the timezone and time
            clone = new GregorianCalendar();
            clone.setTimeInMillis(cal.getTimeInMillis());
            TimeZone tz = cal.getTimeZone();
            clone.setTimeZone(TimeZone.getTimeZone(tz.getID()));
        }
        return clone;
    }
    @Override
    public boolean isMutable() {
        return true;
    }
    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        Calendar cal = null;
        if (value != null) {
            doInstanceCheck(value);
            cal = (Calendar) deepCopy(value);
        }
        return cal;
    }
    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        return disassemble(cached);
    }
    @Override
    public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
        return disassemble(original);
    }
    protected void doInstanceCheck(Object value) {
        if ((value != null) && !(value instanceof Calendar)) {
            throw new UnsupportedOperationException(value.getClass()
                    + " not supported, expecting type "
                    + Calendar.class.getName());
        }
    }
    /**
     * Converts a String 2010-09-26 11:30:00 Australia/Adelaide to
     * Calendar
     *
     * @param timeWithZone
     * @return
     */
    private Calendar getTimeWithZone(String timeWithZone) {
        String timeZoneId = timeWithZone.split("s")[2];
        TimeZone tz = TimeZone.getTimeZone(timeZoneId);
        String format = "yyyy-MM-dd HH:mm:ss";
        DateFormat df = new SimpleDateFormat(format);
        df.setTimeZone(tz);
        try {
            df.parse(timeWithZone);
        } catch (ParseException e) {
            LOG.error("could not parse date string: " + timeWithZone, e);
        }
        return df.getCalendar();
    }
    private String getTimeWithZone(Calendar timeWithZone) {
        String format = "yyyy-MM-dd HH:mm:ss zzzz";
        DateFormat df = new SimpleDateFormat(format);
        df.setTimeZone(timeWithZone.getTimeZone());
        return df.format(timeWithZone.getTime());
    }
}

Hibenate Entity

@Entity
@Table(name = "CUSTOM_DEMO")
public class CustomDemo {
    @Id
    @SequenceGenerator(name = "seq", allocationSize = 1, initialValue = 1, sequenceName = "CUSTOM_DEMO_SEQ")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
    private long id;
    @Column
    private String name;
    @Column(name = "TIME_WITH_ZONE")
    @Type(type = "com.swayam.demo.oracle.hibernate.TimeWithZone")
    private Calendar timeWithZone;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Calendar getTimeWithZone() {
        return timeWithZone;
    }
    public void setTimeWithZone(Calendar timeWithZone) {
        this.timeWithZone = timeWithZone;
    }
}

Resources

The sources can be found https://github.com/paawak/blog/tree/master/code/HibernateCustomDataType