用Hibernate映射INT字段到枚举类型

当实体中包含类似XxxxType字段时,为了性能上的考虑,一般数据库里的字段类型为TINYINT或INT。而自从Java提供枚举类型以后,在Java代码中使用枚举类型来表示这种变量会更好。于是自然要在Hibernate里把TINYINT字段映射到枚举类型。我对Hibernate也不熟,网上找了一下发现内容很少,而且能找到的大多是把枚举类型映射到字符串类型的字段。挺奇怪的,难道大家还是在用定义一堆finalstaticint的常量来表示XxxxType么?

不管怎样,只好根据找到的最靠谱的代码再修改了一下。最终效果还不错。Bean的写法如下,枚举类型需要实现IntegerValuedEnum接口即可。

public class Test {
    public enum TestType implements IntegerValuedEnum {
        Type1(1), Type2(2), Type3(3);
        
        private final int code;
        private TestType(int code) {
            this.code = code;
        }
        @Override
        public int getCode() {
            return code;
        }
    }
    
    private int id;
    private TestType testType;
	
    // Other setters and getters
}

在映射xml文件中如下配置testType:

<property name="testType" column="TEST_TYPE">
    <type name="com.buzzinate.bshare.hibernate.IntegerValuedEnumType">
        <param name="enum">com.buzzinate.bshare.beans.Test$TestType</param>
    </type>
</property>

以下是实现代码。

IntegerValuedEnum接口:

public interface IntegerValuedEnum {    
    int getCode();
}

IntegerValuedEnumReflect类

/**
 * Utility class designed to inspect IntegerValuedEnums.
 */
public final class IntegerValuedEnumReflect {

    /**
     * Don't let anyone instantiate this class.
     * 
     * @throws UnsupportedOperationException
     *             Always.
     */
    private IntegerValuedEnumReflect() {
        throw new UnsupportedOperationException("This class must not be instanciated.");
    }

    /**
     * All Enum constants (instances) declared in the specified class.
     * 
     * @param enumClass        Class to reflect
     * @return Array of all declared EnumConstants (instances).
     */
    private static <T extends Enum> T[] getValues(Class<T> enumClass) {
        return enumClass.getEnumConstants();
    }

    /**
     * All possible string values of the string valued enum.
     * 
     * @param enumClass        Class to reflect.
     * @return Available integer values.
     */
    public static <T extends Enum & IntegerValuedEnum> int[] getStringValues(
            Class<T> enumClass) {
        T[] values = getValues(enumClass);
        int[] result = new int[values.length];
        for (int i = 0; i < values.length; i++) {
            result[i] = values[i].getCode();
        }
        return result;
    }

    /**
     * Name of the enum instance which hold the respecified string value. If
     * value has duplicate enum instances than returns the first occurrence.
     * 
     * @param enumClass        Class to inspect.
     * @param value            The int value.
     * @return name of the enum instance.
     */
    public static <T extends Enum & IntegerValuedEnum> String getNameFromValue(
            Class<T> enumClass, int value) {
        T[] values = getValues(enumClass);
        for (int i = 0; i < values.length; i++) {
            if (values[i].getCode() == value) {
                return values[i].name();
            }
        }
        return "";
    }
}

IntegerValuedEnumType类,也是最主要的类,实现了Hibernate的UserType接口。

public class IntegerValuedEnumType<T extends Enum & IntegerValuedEnum> implements
        EnhancedUserType, ParameterizedType {

    /**
     * Enum class for this particular user type.
     */
    private Class<T> enumClass;

    /**
     * Value to use if null.
     */
    private Integer defaultValue;

    public IntegerValuedEnumType() { }

    public void setParameterValues(Properties parameters) {
        String enumClassName = parameters.getProperty("enum");
        try {
            enumClass = (Class<T>) Class.forName(enumClassName).asSubclass(Enum.class)
                    .asSubclass(IntegerValuedEnum.class); 
        } catch (ClassNotFoundException e) {
            throw new HibernateException("Enum class not found", e);
        }
        
        String defaultValueStr = parameters.getProperty("defaultValue");
        if (defaultValueStr != null && !defaultValueStr.isEmpty()) {
            try {
                setDefaultValue(Integer.parseInt(defaultValueStr));
            } catch (NumberFormatException e) {
                throw new HibernateException("Invalid default value", e);
            }
        }
    }

    public Integer getDefaultValue() {
        return defaultValue;
    }

    public void setDefaultValue(Integer defaultValue) {
        this.defaultValue = defaultValue;
    }

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

    public int[] sqlTypes() {
        return new int[] { Types.TINYINT };
    }

    public boolean isMutable() {
        return false;
    }

    /**
     * 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 owner     the containing entity
     * @return Object
     * @throws HibernateException
     * @throws SQLException
     */
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
        throws SQLException {
        
        Integer value = rs.getInt(names[0]);
        if (value == null) {
            value = getDefaultValue();
            if (value == null) { // no default value
                return null;
            }
        }
        String name = IntegerValuedEnumReflect.getNameFromValue(enumClass, value);
        Object res = rs.wasNull() ? null : Enum.valueOf(enumClass, name);

        return res;
    }

    /**
     * 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
     * @throws HibernateException
     * @throws SQLException
     */
    public void nullSafeSet(PreparedStatement st, Object value, int index)
        throws SQLException {
        
        if (value == null) {
            st.setNull(index, Types.TINYINT);
        } else {
            st.setInt(index, ((T) value).getCode());
        }
    }

    public Object assemble(Serializable cached, Object owner) {
        return cached;
    }

    public Serializable disassemble(Object value) {
        return (Enum) value;
    }

    public Object deepCopy(Object value) {
        return value;
    }

    public boolean equals(Object x, Object y) {
        return x == y;
    }

    public int hashCode(Object x) {
        return x.hashCode();
    }

    public Object replace(Object original, Object target, Object owner) {
        return original;
    }

    public String objectToSQLString(Object value) {
        return '\'' + String.valueOf(((T) value).getCode()) + '\'';
    }

    public String toXMLString(Object value) {
        return String.valueOf(((T) value).getCode());
    }

    public Object fromXMLString(String xmlValue) {
        Integer value = Integer.parseInt(xmlValue);
        String name = IntegerValuedEnumReflect.getNameFromValue(enumClass, value);
        return Enum.valueOf(enumClass, name);
    }
}

Reference:

http://community.jboss.org/wiki/Java5StringValuedEnumUserType

相关推荐