Hibernate & UserType: How to persist JSON objects

A Hibernate UserType defines a (custom) way how to save Java objects into the database. Basically, it defines how the object is serialized to be persisted in the database and how the object is (re)created from the serialized data stored in the database.

Here is a UserType that I’ve written to persist objects of type JSONObject. I’m using the JSON implementation from json.org; for other JSON libraries the UserType should be very similar.

package net.janjonas.example;
 
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.usertype.UserType;
import org.json.JSONException;
import org.json.JSONObject;
 
/**
 * Hibernate user type to persist JSONObject
 *
 * @see http://www.json.org/javadoc/org/json/JSONObject.html
 * @see http://docs.jboss.org/hibernate/stable/annotations/api/org/hibernate/usertype/UserType.html
 * @author "Jan Jonas <mail@janjonas.net>"
 */
public class JSONObjectUserType implements UserType {
 
  private static final int[] SQL_TYPES = { Types.LONGVARCHAR };
 
  @Override
  public Object assemble(Serializable cached, Object owner) throws HibernateException {
    return deepCopy(cached);
  }
 
  @Override
  public Object deepCopy(Object value) throws HibernateException {
    if (value == null) return value;
    try {
      return new JSONObject(((JSONObject)value).toString());
    } catch (JSONException e) {
      throw new RuntimeException(e);
    }
  }
 
  @Override
  public Serializable disassemble(Object value) throws HibernateException {
    return ((JSONObject)value).toString();
  }
 
  @Override
  public boolean equals(Object x, Object y) throws HibernateException {
    if (x == null) return (y != null);
    return (x.equals(y));
  }
 
  @Override
  public int hashCode(Object x) throws HibernateException {
    return ((JSONObject)x).toString().hashCode();
  }
 
  @Override
  public boolean isMutable() {
    return true;
  }
 
  @Override
  public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
    if (!rs.wasNull()) {
      try {
        return new JSONObject(rs.getString(names[0]));
      } catch (JSONException e) {
        throw new RuntimeException(e);
      }
    }
    return null;
  }
 
  @Override
  public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
    if (value == null) {
      st.setNull(index, SQL_TYPES[0]);
    } else {
      st.setString(index, ((JSONObject)value).toString());
    }
  }
 
  @Override
  public Object replace(Object original, Object target, Object owner) throws HibernateException {
    return deepCopy(original);
  }
 
  @Override
  @SuppressWarnings("unchecked")
  public Class returnedClass() {
    return JSONObject.class;
  }
 
  @Override
  public int[] sqlTypes() {
    return SQL_TYPES;
  }
 
}

Comments

  1. Simon

    very useful, thanks for the info!

    couple of things I’ve discovered:

    I think there’s a bug in equals() – should it be:?
    if (x == null) return (y == null);

    also I think there’s a bug in nullSafeGet(): rs.wasNull() refers to the last call, not the one it’s about to read.

    this will return null if the last column read returned null, ie some other random column that hibernate happened to read from its resultset immediately prior to this one.

    a fix would be to check the results of the rs.getString() for null and return a null JSONObject. or call wasNull() after the getString().

  2. ajay mittal

    please provide the hbm.xml file sample also.
    I have used :

    which leads to following exception:
    Exception in thread “main” java.lang.ExceptionInInitializerError
    at utils.HibernateUtils.buildSessionFactory(HibernateUtils.java:20)
    at utils.HibernateUtils.(HibernateUtils.java:6)
    at client.Client.main(Client.java:20)
    Caused by: org.hibernate.MappingException: Could not determine type for: JSONObjectUserType, at table: ELEMENT, for columns: [org.hibernate.mapping.Column(jsonObject)]
    at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:292)
    at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:276)
    at org.hibernate.mapping.Property.isValid(Property.java:207)
    at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:458)
    at org.hibernate.mapping.RootClass.validate(RootClass.java:215)
    at org.hibernate.cfg.Configuration.validate(Configuration.java:1135)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1320)
    at utils.HibernateUtils.buildSessionFactory(HibernateUtils.java:17)
    … 2 more

  3. Adriano Santos

    Hi, I’m trying to use this sample, but I always received the folow error:
    ERROR: column “Tramites” is the type json but expression is of type character varying
       Hint: You will need to rewrite or cast the expression.

    May you help me?

Leave a comment

Your email address will not be published. Required fields are marked *

*