將Hibernate中的枚舉轉換爲自定義數值

問題

在編碼過程當中,常常會遇到用某個數值來表示某種狀態、類型或者階段的狀況,好比有這樣一個枚舉:html

public enum ComputerState {
    OPEN(10),         //開啓
    CLOSE(11),         //關閉
    OFF_LINE(12),     //離線
    FAULT(200),     //故障
    UNKNOWN(255);     //未知

    private int code;
    ComputerState(int code) { this.code = code; }
}

一般咱們但願將表示狀態的數值存入數據庫,即ComputerState.OPEN存入數據庫取值爲10java

探索

首先,咱們先看看Hibernate是否可以知足咱們的需求。
Hibernate內置了org.hibernate.type.EnumType轉換器,可將枚舉轉換爲NamedOrdinal
這樣使用它:sql

// 將ComputerState.OPEN轉換OPEN
@Enumerated(EnumType.STRING)
private ComputerState state;
// ComputerState.OPEN轉換爲0,ComputerState.CLOSE轉換爲1
@Enumerated(EnumType.STRING)
private ComputerState state;

以上的兩種方式不能知足咱們的需求,看起來要本身實現轉換的過程了。數據庫

準備工做

首先,咱們須要作一些準備工做,便於在枚舉和code之間轉換。segmentfault

1. 定義接口

咱們須要一個接口來肯定某部分枚舉類的行爲。以下:緩存

public interface BaseCodeEnum {
    int getCode();
}

該接口只有一個返回編碼的方法,返回值將被存入數據庫。sass

2. 改造枚舉

就拿上面的ComputerState來實現BaseCodeEnum接口:session

public enum ComputerState implements BaseCodeEnum{
    OPEN(10),         //開啓
    CLOSE(11),         //關閉
    OFF_LINE(12),     //離線
    FAULT(200),     //故障
    UNKNOWN(255);     //未知

    private int code;
    ComputerState(int code) { this.code = code; }

    @Override
    public int getCode() { return this.code; }
}

3. 編寫一個轉換工具類

如今咱們能順利的將枚舉轉換爲某個數值了,還須要一個工具將數值轉換爲枚舉實例。ide

public class CodeEnumUtil {

    public static <E extends Enum<?> & BaseCodeEnum> E codeOf(Class<E> enumClass, int code) {
        E[] enumConstants = enumClass.getEnumConstants();
        for (E e : enumConstants) {
            if (e.getCode() == code)
                return e;
        }
        return null;
    }
}

至此,準備工做完成。接下來須要在Hibernate中完成對枚舉的轉換。工具

方案1:AttributeConverter

Hibernate提供了javax.persistence.AttributeConverter<X,Y>接口指定如何將實體屬性轉換爲數據庫列表示。

此方案適用與數量很少或者個別特殊的枚舉。

須要實現兩個方法:

  1. public Y convertToDatabaseColumn (X attribute);
    該方法指定如何將實體屬性轉換爲數據庫列屬性
  2. public X convertToEntityAttribute (Y dbData);
    該方法指定如何將數據庫列屬性轉換爲實體屬性

我是這樣實現的:

public class CodeEnumConverter implements AttributeConverter<ComputerState,Integer> {
    @Override
    public Integer convertToDatabaseColumn(ComputerState attribute) {
        return attribute.getCode();
    }

    @Override
    public ComputerState convertToEntityAttribute(Integer dbData) {
        return CodeEnumUtil.codeOf(ComputerState.class,dbData);
    }
}

這樣使用:

@Convert( converter = CodeEnumConverter.class )
private ComputerState state;

方案2:UserType

除了AttributeConverter還提供了一個用戶自定義類型的接口:org.hibernate.usertype.UserType
注意! 這裏的類型不是一個實際的屬性類型,而是一個知道如何將數據類型序列化到JDBC的類!

此方案適用於具備類似行爲的一組枚舉。

須要實現如下方法:

  1. public int[] sqlTypes()
    返回由該類型映射列的SQL類型代碼。
  2. public Class returnedClass()
    指定由SQL類型轉換成哪一種數據類型
  3. public boolean equals(Object x, Object y) throws HibernateException;
    數據類型之間的比對
  4. public int hashCode(Object x) throws HibernateException;
    將數據類型轉換爲HashCode
  5. public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException;
    從JDBC ResultSet讀取數據,將其轉換爲數據類型後返回,須要處理NULL值。
  6. public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException;
    將數據類型轉換爲SQL類型
  7. public Object deepCopy(Object value) throws HibernateException;
    深度拷貝
  8. public boolean isMutable();
    類型是否可變
  9. public Serializable disassemble(Object value) throws HibernateException;
    將對象轉換爲可緩存的表示形式
  10. public Object assemble(Serializable cached, Object owner) throws HibernateException;
    從緩存中重建一個對象。
  11. public Object replace(Object original, Object target, Object owner) throws HibernateException;
    在合併過程當中,將正在合併的實體中的現有(目標)值替換爲正在合併的分離實體的新(原始)值。

我是這樣實現的(參考了org.hibernate.type.EnumType):

public class CodeEnumType<E extends Enum<?> & BaseCodeEnum> implements UserType, DynamicParameterizedType {

    private static final int SQL_TYPE = Types.INTEGER;
    private static final String ENUM = "enumClass";

    private Class<E> enumClass;

    @Override
    public void setParameterValues(Properties parameters) {
        final ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);

        if (reader != null) {
            enumClass = reader.getReturnedClass().asSubclass(Enum.class);
        } else {
            final String enumClassName = (String) parameters.get(ENUM);
            try {
                enumClass = ReflectHelper.classForName(enumClassName, this.getClass()).asSubclass(Enum.class);
            } catch (ClassNotFoundException exception) {
                throw new HibernateException("Enum class not found: " + enumClassName, exception);
            }
        }
    }


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

    @Override
    public Class returnedClass() {
        return enumClass;
    }

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

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x == null ? 0 : x.hashCode();
    }

    @Override
    public E nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
        final int value = rs.getInt(names[0]);
        return rs.wasNull() ? null : codeOf(value);
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
        st.setObject(index, ((BaseCodeEnum) value).getCode(), SQL_TYPE);
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

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

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

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

    private E codeOf(int code) {
        try {
            return CodeEnumUtil.codeOf(enumClass, code);
        } catch (Exception ex) {
            throw new IllegalArgumentException("Cannot convert " + code + " to " + enumClass.getSimpleName() + " by code value.", ex);
        }
    }


}

其中實現了DynamicParameterizedType.setParameterValues方法,是爲了獲取具體的子類。

這樣使用:

@Type(type = "com.example.CodeEnumType")
private ComputerState state;

結束了

很久沒有摸Hibernate了,生疏了不少。若是你還有更優的解決方案,請必定在評論中告知,萬分感激。

在Mybatis中使用枚舉能夠看這裏

參考資料:
Hibernate User Guide

相關文章
相關標籤/搜索