将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

相关文章
相关标签/搜索