2013年1月18日 星期五

Customize Hibernate JSON UserType & Apply Package Level Annotation

這裡我們展示如何自訂一個generic UserType,功能是將entity的任意物件轉換成JSON之後以String的方式儲存到database中,然後提供一個sample供參考,而這個sample會以package level的方式設定。
GenericJsonStringType<T>
package com.gss.gmo.cao.hibernate.usertype;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Date;

import org.apache.commons.lang3.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.gss.gmo.cao.gson.ISODateTimeAdapter;

public abstract class GenericJsonStringType<T> implements UserType {

    private static final int[] SQL_TYPES = { Types.LONGVARCHAR };

    private final Type userType;

    private final Gson gson;

    public GenericJsonStringType() {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType pt = (ParameterizedType) type;
        userType = pt.getActualTypeArguments()[0];
        gson = new GsonBuilder().registerTypeAdapter(Date.class, new ISODateTimeAdapter()).create();
    }

    @Override
    public int[] sqlTypes() {
        return SQL_TYPES;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Class returnedClass() {
        if (userType instanceof Class) {
            return (Class) userType;
        } else {
            return (Class) ((ParameterizedType) userType).getRawType();
        }
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return ObjectUtils.equals(x, y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
        String jsonString = rs.getString(names[0]);
        return gson.fromJson(jsonString, userType);
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
        st.setString(index, gson.toJson(value, userType));
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return gson.fromJson(gson.toJson(value, userType), userType);
    }

    @Override
    public boolean isMutable() {
        return true;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return gson.toJson(value, userType);
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return gson.fromJson((String) cached, userType);
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

}
Sample有四個部分:
  1. Setup org.springframework.orm.hibernate4.LocalSessionFactoryBean
  2. Setup org.hibernate.annotations.TypeDef in package-info.java
  3. Extend GenericJsonStringType<T>
  4. Mapping UserType field in entity
Setup org.springframework.orm.hibernate4.LocalSessionFactoryBean
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource">
        <ref bean="dataSource" />
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
        </props>
    </property>
    <property name="packagesToScan" value="com.gss.gmo.cao.hibernate.impl" />
    <property name="annotatedPackages" value="com.gss.gmo.cao.hibernate.impl" />
</bean>
Setup org.hibernate.annotations.TypeDef in package-info.java
@TypeDef(name = "listType", typeClass = ListJsonType.class)
package com.gss.gmo.cao.hibernate.impl;

import org.hibernate.annotations.TypeDef;
Extend GenericJsonStringType<T>
package com.gss.gmo.cao.hibernate.impl;

import java.util.List;
import com.gss.gmo.cao.hibernate.usertype.GenericJsonStringType;

public class ListJsonType extends GenericJsonStringType<List<String>> {
}
Mapping UserType field in entity
package com.gss.gmo.cao.hibernate.impl;

import java.util.List;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import org.hibernate.annotations.Type;

import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.hibernate.audit.AuditInfo;

/**
 * @author linus_chien
 *
 */
@Getter
@Setter
@EqualsAndHashCode(callSuper = false, of = { "id" })
@ToString
@Entity
public class Person extends BaseDto {

    private static final long serialVersionUID = 1L;

    @Id
    private String id;

    @Column
    private String name;

    @Column
    private String address;

    @Embedded
    private AuditInfo auditInfo;

    @Type(type = "listType")
    private List<String> emails;

}