首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Hibernate 自定義類型 UserType 實戰:將 List 映射為單欄字串

     編輯:Java 相關知識

何時需要自定義類型?

當 Hibernate 內建型別(String、Integer、Date 等)無法滿足物件模型與資料表結構不匹配時,需實作 org.hibernate.usertype.UserType 介面,自定義Java 物件 ⇄ 資料庫欄位的序列化/反序列化邏輯。


核心介面方法解析

方法職責典型實作
sqlTypes()返回資料庫欄位 SQL 型別碼(java.sql.Types)new int[]{Types.VARCHAR}
returnedClass()返回 Java 端型別List.class
nullSafeGet(ResultSet, names, owner)從 ResultSet 讀取並轉為 Java 物件解析字串為 List
nullSafeSet(PreparedStatement, value, index)將 Java 物件寫入 PreparedStatement序列化 List 為字串
deepCopy(value)返回深拷貝,避免共享可變物件new ArrayList<>(list)
isMutable()型別是否可變,影響快取策略true(集合通常可變)
equals(x, y) / hashCode(x)持久化狀態的相等性判斷比較集合內容
disassemble/assemble二級快取序列化/反序列化可委託 deepCopy
replace(original, target, owner)合併脫管物件時替換值返回 deepCopy(original)

實戰案例:EmailList 類型

需求:User 實體含 List emails,資料表單欄 emails VARCHAR 以分號 ; 分隔儲存。

1. 實作 UserType

public class EmailListType implements UserType {
    private static final String DELIMITER = ";";
    private static final int[] SQL_TYPES = {Types.VARCHAR};
    
    @Override public int[] sqlTypes() { return SQL_TYPES; }
    @Override public Class returnedClass() { return List.class; }
    @Override public boolean isMutable() { return true; }
    
    // 反序列化:資料庫字串 → List
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) 
            throws HibernateException, SQLException {
        String val = (String) Hibernate.STRING.nullSafeGet(rs, names[0]);
        if (val == null || val.isEmpty()) return Collections.emptyList();
        return Arrays.asList(val.split(DELIMITER));
    }
    
    // 序列化:List → 資料庫字串
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index) 
            throws HibernateException, SQLException {
        if (value == null) {
            Hibernate.STRING.nullSafeSet(st, null, index);
            return;
        }
        List list = (List) value;
        String joined = String.join(DELIMITER, list);
        Hibernate.STRING.nullSafeSet(st, joined, index);
    }
    
    @Override public Object deepCopy(Object value) {
        if (value == null) return null;
        return new ArrayList<>((List) value);
    }
    
    @Override public boolean equals(Object x, Object y) {
        if (x == y) return true;
        if (x == null || y == null) return false;
        return ((List) x).equals((List) y);
    }
    
    @Override public int hashCode(Object x) {
        return x == null ? 0 : x.hashCode();
    }
    
    @Override public Serializable disassemble(Object value) {
        return (Serializable) deepCopy(value);
    }
    @Override public Object assemble(Serializable cached, Object owner) {
        return deepCopy(cached);
    }
    @Override public Object replace(Object original, Object target, Object owner) {
        return deepCopy(original);
    }
}

2. 映射文件配置

<class name="User" table="t_user">
    <id name="id" column="user_id"><generator class="native"/></id>
    <property name="name" column="user_name"/>
    <!-- 指定自定義類型全限定名 -->
    <property name="emails" column="emails" type="com.example.type.EmailListType"/>
</class>

3. 測試驗證

User user = new User();
user.setName("shun");
user.setEmails(Arrays.asList("a@b.com", "c@d.com"));
session.save(user); // 執行 INSERT,emails 欄位存 "a@b.com;c@d.com"

User loaded = session.get(User.class, user.getId());
System.out.println(loaded.getEmails()); // [a@b.com, c@d.com]

常見坑點與修正

問題:單元素 List 導致 ClassCastException

現象:儲存單個 email 時,資料庫讀回時發生 ArrayList cannot be cast to String。

原因:Hibernate 內部機制在某些版本下,單元素集合可能被雙重包裝。

修正:在 nullSafeGet 中加強防禦:

List result = new ArrayList<>();
if (val != null) {
    for (String s : val.split(DELIMITER)) {
        if (!s.isEmpty()) result.add(s);
    }
}
return result;

進階:CompositeUserType

若需將單一 Java 物件映射為多個資料庫欄位(如 Address → street, city, zip),實作 CompositeUserType,需額外實作:

  • getPropertyNames()、getPropertyTypes()
  • getPropertyValue(object, i)、setPropertyValue(object, i, value)

適用於值物件拆分映射場景。


最佳實踐

  1. 自定義類型類命名規範:XxxType,放在 type 包下。
  2. nullSafeGet/Set 委託對應基本型別(Hibernate.STRING 等)處理 null。
  3. deepCopy 必須返回新實例,確保實體間狀態隔離。
  4. isMutable() 誠實標註,影響二級快取行為。
  5. 優先考慮 JPA @Convert / AttributeConverter(Hibernate 5+),更標準、可移植。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved