PostgreSQL json object mapping through Hibernate

User defined datatype in hibernate

Posted on August 14, 2014

Hibernate make it relatively easy for developers to make their own value type. It is one of the best feature of hibernate and well documented. PostgreSQL have many kind of data type and JSON data type is one of them, and provide some flexibility like NoSQL database in your relational database design.

Below is the step by step implementation of this feature. All the class in this example contain in a eclipse project and you can download it form the GitHub.

First of all you need to implement Hibernate UserType interface which provide contract to implement your own datatype. Class JSONObjectUserType Implements hibernate UserType interface to provide functionality to hibernate for change from java object to PostgreSQL acceptable type.

package com.vivekpatidar.postgres.demo;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
import org.json.JSONObject;

public class JSONObjectUserType implements UserType {

    /**
     * Reconstruct an object from the cacheable representation. At the very
     * least this method should perform a deep copy if the type is mutable.
     * (optional operation)
     *
     * @param cached
     *            the object to be cached
     * @param owner
     *            the owner of the cached object
     * @return a reconstructed object from the cachable representation
     * @throws HibernateException
     */
    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return this.deepCopy(cached);
    }

    /**
     * Return a deep copy of the persistent state, stopping at entities and st
     * collections. It is not necessary to copy immutable objects, or null
     * values, in which case it is safe to simple return the argument.
     *
     * @param value
     *            the object to be cloned, which may be null
     *
     * @return object a copy
     * @throws HibernateException
     */
    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    /**
     * Transform the object into its cacheable representation. At the very least
     * this method should perform a deep copy if the type is mutable. That may
     * not be enough for some implementations, however; for example,
     * associations must be cached as identifier values. (optional operation)
     *
     * @param value
     *            the object to be cached
     * @return a cachable representation of the object
     * @throws HibernateException
     */
    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (String) this.deepCopy(value);
    }

    /**
     * Compare two instances of the class mapped by this type for persistence
     * "equality". Equality of the persistence state.
     *
     * @param x
     * @param y
     * @return boolean
     * @throws HibernateException
     */
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {

        if (x == null) {
            return y == null;
        }
        return x.equals(y);
    }

    /**
     * Get a hashcode for the instance, consistent with persistence "equality".
     */
    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    /**
     * Are objects of this type mutable?
     *
     * @return boolean
     */
    @Override
    public boolean isMutable() {
        return true;
    }

    /**
     * Retrieve an instance of the mapped class from a JDBC resultset.
     * Implementors should handle possibility of null values.
     *
     * @param rs
     *            a JDBC result set
     * @param names
     *            the column names
     * @param session
     * @param owner
     *            the containing entity
     * @return
     * @throws HibernateException
     * @throws SQLException
     */
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
        JSONObject jsonObject = new JSONObject(rs.getString(names[0]));
        return jsonObject;
    }

    /**
     * Write an instance of the mapped class to a prepared statement.
     * Implementors should handle possibility of null values. A multi-column
     * type should be written to parameters starting from <tt>index</tt>
     *
     * @param st
     *            a JDBC prepared statement
     * @param value
     *            the object to write
     * @param index
     *            statement parameter index
     * @param session
     * @throws HibernateException
     * @throws SQLException
     */
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {

        if (value == null) {
            st.setNull(index, Types.OTHER);
            return;
        }

        st.setObject(index, value, Types.OTHER);
    }

    /**
     * During merge, replace the existing (target) values in the entity we are
     * merging to with a new (original) value from the detched entity we are
     * merging. For immutable objects, or null values, it is safe to return a
     * copy of the first parameter. For the objects with component values, it
     * might make sense to recursively replace component values
     *
     * @param original
     *            the value from the detched entity being merged
     * @param target
     *            the value in the managed entity
     * @param owner
     * @return the value to be merged
     * @throws HibernateException
     */
    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

    /**
     * The class returned by <tt>nullSafeGet()</tt>
     *
     * @return Class
     */
    @Override
    public Class returnedClass() {
        return String.class;
    }

    /**
     * Returns the SQL type codes for the columns mapped by this type. The codes
     * are defined on <tt>java.sql.Types</tt>
     *
     * @return int[] the typecodes
     * @see java.sql.Types
     */
    @Override
    public int[] sqlTypes() {
        return new int[] { Types.JAVA_OBJECT };
    }

}
Custom Dialect : After implementing hibernate UserType interface hibernate should know to use this implementation for a particular datatype. For this purpose we need to implement custom dialect which extends default dialect of PostgreSQL and register user defined type.
package com.vivekpatidar.postgres.demo;

import java.sql.Types;

import org.hibernate.dialect.PostgreSQL9Dialect;

public class JsonPostgreSQLDialect extends PostgreSQL9Dialect {

    public JsonPostgreSQLDialect() {
        super();
        this.registerColumnType(Types.JAVA_OBJECT, "json");
    }

}
At this point all the basic requirement has been done. Further we need to change in hibernate entity class to annotate required field with this implementation.
package com.vivekpatidar.postgres.demo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.json.JSONObject;

@Entity
@TypeDefs({ @TypeDef(name = "CustomJsonObject", typeClass = JSONObjectUserType.class) })
public class UserContact {

    @Id
    @GeneratedValue
    private Integer id;

    @Type(type = "CustomJsonObject")
    private JSONObject contact;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public JSONObject getContact() {
        return contact;
    }

    public void setContact(JSONObject contact) {
        this.contact = contact;
    }

    @Override
    public String toString() {
        return "UserContact [id=" + id + ", contact=" + contact + "]";
    }

}
Now, replace the default dialect with the new user defined dialect in hibernate utility class.
package com.vivekpatidar.postgres.demo;

import java.util.Properties;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class PostgresHibernateUtil {
    private static Configuration configuration = new Configuration();
    private static SessionFactory sessionFactory;
    private static Properties prop;

    private static SessionFactory buildSessionFactory() {
        try {

            prop = new Properties();

            String url = "127.0.0.1:5432/hstorecurddemo";
            /*
             * This is default postgres dilect need to be replace.
             * prop.put("hibernate.dialect", * "org.hibernate.dialect.PostgreSQLDialect");
             */
            prop.put("hibernate.dialect", "com.vivekpatidar.postgres.demo.JsonPostgreSQLDialect");
            prop.put("hibernate.connection.driver_class", "org.postgresql.Driver");
            prop.put("hibernate.connection.url", "jdbc:postgresql://" + url);

            prop.put("hibernate.connection.username", "postgres");
            prop.put("hibernate.connection.password", "root");

            prop.put("hibernate.show_sql", "true");
            prop.put("hibernate.hbm2ddl.auto", "update");
            prop.put("hibernate.connection.isolation", "2");

            configuration.setProperties(prop);

            configuration.addAnnotatedClass(UserContact.class);

            return configuration.buildSessionFactory();
        } catch (Throwable ex) {
            System.out.println("Initital session factory build failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return buildSessionFactory();
    }

    public static Session getSession() {
        return sessionFactory.getCurrentSession();
    }

}
Now you can use from here all function provided by hibernate e.g. save(), delete() etc. This implementation will take care about all the required details.