问题
在编码过程中,经常会遇到用某个数值来表示某种状态、类型或者阶段的情况,比如有这样一个枚举:
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
存入数据库取值为10
。
探索
首先,我们先看看Hibernate是否能够满足我们的需求。
Hibernate内置了org.hibernate.type.EnumType
转换器,可将枚举转换为Named
或Ordinal
。
这样使用它:
// 将ComputerState.OPEN转换OPEN
@Enumerated(EnumType.STRING)
private ComputerState state;
// ComputerState.OPEN转换为0,ComputerState.CLOSE转换为1
@Enumerated(EnumType.STRING)
private ComputerState state;
以上的两种方式不能满足我们的需求,看起来要自己实现转换的过程了。
准备工作
首先,我们需要做一些准备工作,便于在枚举和code
之间转换。
1. 定义接口
我们需要一个接口来确定某部分枚举类的行为。如下:
public interface BaseCodeEnum {
int getCode();
}
该接口只有一个返回编码的方法,返回值将被存入数据库。
2. 改造枚举
就拿上面的ComputerState
来实现BaseCodeEnum
接口:
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. 编写一个转换工具类
现在我们能顺利的将枚举转换为某个数值了,还需要一个工具将数值转换为枚举实例。
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>
接口指定如何将实体属性转换为数据库列表示。
此方案适用与数量不多或者个别特殊的枚举。
需要实现两个方法:
-
public Y convertToDatabaseColumn (X attribute);
该方法指定如何将实体属性转换为数据库列属性 -
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的类!
此方案适用于具有相似行为的一组枚举。
需要实现以下方法:
-
public int[] sqlTypes()
返回由该类型映射列的SQL类型代码。 -
public Class returnedClass()
指定由SQL类型转换成哪种数据类型 -
public boolean equals(Object x, Object y) throws HibernateException;
数据类型之间的比对 -
public int hashCode(Object x) throws HibernateException;
将数据类型转换为HashCode -
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException;
从JDBC ResultSet读取数据,将其转换为数据类型后返回,需要处理NULL值。 -
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException;
将数据类型转换为SQL类型 -
public Object deepCopy(Object value) throws HibernateException;
深度拷贝 -
public boolean isMutable();
类型是否可变 -
public Serializable disassemble(Object value) throws HibernateException;
将对象转换为可缓存的表示形式 -
public Object assemble(Serializable cached, Object owner) throws HibernateException;
从缓存中重建一个对象。 -
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。