javahibernategenericsusertype

Hibernate + custom usertypes using generics


I am new to hibernate. I am using postgres as the DB.

I have a table User. which contains a column called metadata which is of type jsonb.

I would like to map this column to an object (serializable) Metadata. I read through some tutorials and came to know that we need to implement custom userType to achieve this.

So I implemented MyMetadataType. Now I have another column called settings again of jsonb type. To map this column to its corresponding Object I need another userType implementation.

Is it possible to have a generic class like the following.. Just one class for all such columns?

class MyCustomType<T> implements UserType
{
...
...
}

if yes then how will I use it in the Entity definition?

@Entity
@Table(name = "user")
@TypeDefs({ @TypeDef(name = "MyCustomType", typeClass = MyCustomType<Metadata>.class) })
public class User extends BaseEntity implements Serializable
{

    @Id
    @Column(name = "id")
    private int id;

    @Column(name = "metadata")
    @Type(type = "MyCustomeType")
    private Metadata metadata;

    .........
    .........
    .........

}

By looking up previous SO questions, I came up with the following class:

public class MyCustomType<T> implements UserType
{
    protected static Conversion conversion = new JsonDataConversionImpl();
    private static final Logger logger = LogManager.getLogger(MyCustomType.class.getCanonicalName());

    @SuppressWarnings("unchecked")
    private Class<T> genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), MyCustomType.class);

    /**
     * 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
    {
        T t = null;
        try
        {

            if (rs.getString(names[0]) != null)
            {
                t = conversion.getObject(rs.getString(names[0]), genericType);
            }
        }
        catch (MyException e)
        {
            logger.error("Error while reading data type", e);
        }

        return t;
    }

    /**
     * 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 };
    }

}

I just need to know how to use this custom type in the User class. Can anyone help me out?


Solution

  • Use the fully qualified name of the Type :

    @Type(type = "package.MyCustomType")

    You also need to use a ParameterizedType :

    @Type(type = "package.MyCustomType",
          parameters = { @Parameter(
                               name = "class", value = "package.Metadata.class") })
    

    In your UserType, you'll have to add this method (implements ParameterizedType interface):

    void setParameterValues(Properties parameters);
    

    where you'll get in parameters, the entry : class=package.Metadata.class

    you'll just have to store this in a field, and modify the standard UserType methods to change behavior base on it.