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 的屬性時則不會造成數據加載的異常,頂多是數據混亂,或有些值無法理解而已。