Hibernate中,以String类型作为主键时,如果报出 detached entity passed to persist: XXX
错误,说明已经指定了id的值。而id是自动生成的
如果查询时枚举类型报出错误 Invalid value for getInt()
,则可以在实体类中枚举类型的属性上加上如下两个语句之一:
@Enumerated(EnumType.ORDINAL)
或 @Enumerated(EnumType.STRING)
EnumType.ORDINAL
:要求被映射的数据库字段类型为整形
Java 中的枚举项都有一个内部的 ordinal 值,从 0 开始编排,不能像 C/C++ 那样定制,所以如果选用这个类型时。持久化时调用 enum.oridinal() 得到整数值,加载数据时按照整数索引找到相应的枚举值。
实现为 OrdinaEnumValueMapper,
持久化时从枚举值转换为整形时调用setValue()方法
public void setValue(PreparedStatement st, Enum value, int index) throws SQLException {
final Object jdbcValue = value == null ? null : extractJdbcValue( value );
if (jdbcValue == null) {
st.setNull( index, getSqlType() );
return;
}
st.setObject( index, jdbcValue, EnumType.this.sqlType );
}
它所调用的 extractJdbcValue(value) 是:
protected Object extractJdbcValue(Enum value) {
return value.ordinal();
}
由上可知 Java 属性值为 null, 数据库字段也会是 NULL, 非 null 时直接调用枚举的 ordinal() 获得整形值。
从数据库中加载数据生成 Java 的枚举值时调用 getValue() 方法
public Enum getValue(ResultSet rs, String[] names) throws SQLException {
final int ordinal = rs.getInt(names[0]);
if (rs.wasNull()) {
return null;
}
return fromOrdinal(ordinal);
}
它调用的 fromOrdinal(ordinal) 方法如下:
private Enum fromOrdinal(int ordinal) {
final Enum[] enumsByOrdinal = enumsByOrdinal();
if (ordinal < 0 || ordinal >= enumsByOrdinal.length) {
throw new IllegalArgumentException(
String.format("Unknown ordinal value [%s] for enum class [%s]", ordinal, enumClass.getName()));
}
return enumsByOrdinal[ordinal];
}
当数据库表中存的值为 NULL, 得到的枚举值也是 null, 当为越界的整形值是报出异常,这是合理的,既然定义该字段映射为 Java 枚举值,那么就不能乱填值。
- EnumType.STRING:要求被映射的数据库字段类型为字符串
就是枚举的字面名 name,持久化时调用 enum.name() 获得这个名称,加载数据时调用 Enum.valueOf() 方法来获得枚举值
实现类为 NamedEnumValueMapper
持久化时从枚举值转换为字符是调用与上同一个 setValue() 方法,只是 extractJdbcValue(value) 不一样
protected Object extractJdbcValue(Enum value) {
return value.name();
}
类似的,如果枚举值为 null, 保存到数据库后也是 NULL, 否则调用枚举的 name() 方法获得字符串存入数据库
从数据库中加载数据生成 Java 的枚举值是调用 getValue() 方法
public Enum getValue(ResultSet rs, String[] names) throws SQLException {
final String value = rs.getString(names[0]);
if (rs.wasNull()) {
return null;
}
return fromName( value );
}
它调用 fromName(value) 方法
private Enum fromName(String name) {
try {
if (name == null) {
return null;
}
return Enum.valueOf( enumClass, name.trim() );
} catch (IllegalArgumentException iae) {
throw new IllegalArgumentException(
String.format("Unknown name value [%s] for enum class [%s]", name, enumClass.getName()));
}
}
数据库中是 NULL 值,没问题,得到的枚举值也是 null, 但非预期的字符串就要报出异常,正常的话调用 Enum.valueOf() 方法获得相应的枚举值。
Hibernate 在使用枚举能安全的进行 null 值映身从分别调用 getValue() 和 setValue() 的入口方法就知道,入口方法各自叫做 nullSafeGet() 和 nullSafeSet()
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
if (enumValueMapper == null ) {
throw new AssertionFailure("EnumType (" + enumClass.getName() + ") not properly, fully configured");
}
return enumValueMapper.getValue( rs, names );
}
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (enumValueMapper == null) {
throw new AssertionFailure("EnumType (" + enumClass.getName() + ") not properly, fully configured");
}
enumValueMapper.setValue(st, (Enum) value, index);
}
以上源代码来自 Hibernate 官方代码库,但进行了重排并移除了日志相关的代码。通过阅读 Hibernate EnumType 源代码我们可以非常的清楚它是如何工作的,以及什么情况下会出现何种状况,也就是千万不去乱改数据库中的值。
使用枚举类型进行映射有一个弊端就是,将来有一天修改了枚举类型的定义会造成数据库中的数据无法被加载,所以如果对改动的枚举定义(如顺序调整了– ORDINAL; 或名称改了; 或增减了选项) 时一定要同步 update 数据库中的记录,这对于产品数据库也是个麻烦事。这一点上还是需要谨慎的思考是否真要用 Java 的枚举值来映射。
而用 Integer 或 String 来作为 Java 的属性时则不会造成数据加载的异常,顶多是数据混乱,或有些值无法理解而已。