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

Java ME RMS 通用持久化框架設計與實作:解決無序列化環境下的物件儲存難題

     編輯:Java 相關知識

前言:Java ME 持久化挑戰

Java ME (J2ME) 環境資源受限:無反射機制、無 java.io.Serializable 介面、無標準序列化。儲存選項僅有:

  1. RMS (Record Management System):所有手機支援、安全無提示、但 API 底層 (位元組陣列操作)
  2. 檔案系統 (JSR-75):需簽名、部分手機不支援、讀寫彈出權限提示

本文設計一個輕量框架,封裝 RMS 底層細節,提供類似 Java SE 序列化的使用體驗。

框架架構:四大核心介面/類別

核心介面設計

1. Serializable 介面:手動序列化契約

public interface Serializable {
    // 將物件編碼為位元組陣列
    byte[] serialize() throws IOException;
    
    // 從位元組陣列還原物件狀態
    void unSerialize(byte[] data) throws IOException;
    
    // 設定/取得 RMS 記錄 ID (插入後由框架回填)
    void setId(int id);
    int getId(); // 未持久化時回傳 -1
}

設計考量: 無反射環境下,由開發者決定欄位編碼順序與格式 (DataOutputStream/DataInputStream),效能最高、體積最小。

2. Lazy 介面:大物件分離儲存

public interface Lazy extends Serializable {
    // 取得指定鍵的附件資料 (如圖片、大文本),由框架在儲存主物件時調用
    byte[] getAttach(Object key) throws IOException;
    
    // 附件儲存完成後回調,通知主物件附件的 RMS ID
    void savedAttach(Object key, int id);
    
    // 列出所有附件鍵
    Object[] getAttachKeys();
    
    // 附件儲存的 RMS 名稱 (獨立 RecordStore)
    String getNameOfAttachRMS();
}

應用場景:聯絡人物件含大頭照、語音備註。主物件 (姓名、電話) 常用快速載入;附件按需載入。

3. RecordFetcher 介面:統一查詢規範


public interface RecordFetcher extends RecordComparator, RecordFilter {
    int getFromIndex();      // 起始索引 (分頁用)
    int getMaxRecordSize();  // 最大筆數 (分頁用)
}

結合 RMS 原生 RecordEnumeration 的比較器與過濾器,加上分頁參數,構建完整查詢語義。

RMSUtil 核心實作解析

RecordStore 快取機制


private static Hashtable rmsCache = new Hashtable(); // 名稱 -> RecordStore

private synchronized static RecordStore getRecordStore(String name) 
        throws RecordStoreException {
    // RMS 名稱限制 32 字元,截取尾段
    if (name.length() > 32) name = name.substring(name.length() - 32);
    
    if (rmsCache.containsKey(name)) 
        return (RecordStore) rmsCache.get(name);
    
    RecordStore rs = RecordStore.openRecordStore(name, true); // 不存在則建立
    rmsCache.put(name, rs);
    return rs;
}

優點:避免重複開啟/關閉 RecordStore 開銷;注意:MIDlet 銷毀時必須呼叫 closeAllRMS() 釋放資源。

核心 CRUD 操作


// 新增
public static boolean insertObject(Serializable ser) {
    RecordStore rs = getRecordStore(ser.getClass().getName());
    if (ser instanceof Lazy) insertAttachDatas((Lazy) ser); // 先存附件
    byte[] data = ser.serialize();
    int id = rs.addRecord(data, 0, data.length);
    ser.setId(id); // 回填 ID
    return true;
}

// 更新
public static boolean updateObject(Serializable ser) {
    RecordStore rs = getRecordStore(ser.getClass().getName());
    byte[] data = ser.serialize();
    rs.setRecord(ser.getId(), data, 0, data.length);
    return true;
}

// 刪除
public static boolean deleteObject(Serializable ser) {
    if (ser.getId() == -1) return false;
    RecordStore rs = getRecordStore(ser.getClass().getName());
    rs.deleteRecord(ser.getId());
    ser.setId(-1);
    return true;
}

// 讀取單筆
public static Serializable readObject(int id, Class clz) {
    RecordStore rs = getRecordStore(clz.getName());
    byte[] data = rs.getRecord(id);
    Serializable ser = (Serializable) clz.newInstance();
    ser.unSerialize(data);
    ser.setId(id);
    return ser;
}

進階查詢:分頁與條件過濾


// 簡單分頁查詢
public static Vector listObjects(Class cls, int from, int maxSize) {
    RecordStore rs = getRecordStore(cls.getName());
    RecordEnumeration ren = rs.enumerateRecords(null, null, false);
    return fetchRecords(ren, cls, from, maxSize);
}

// 條件查詢 (支援排序、過濾)
public static Vector listObjects(Class cls, RecordFetcher fetcher) {
    RecordStore rs = getRecordStore(cls.getName());
    RecordEnumeration ren = rs.enumerateRecords(fetcher, fetcher, false);
    return fetchRecords(ren, cls, fetcher.getFromIndex(), fetcher.getMaxRecordSize());
}

private static Vector fetchRecords(RecordEnumeration ren, Class cls, 
                                   int from, int maxSize) throws Exception {
    Vector v = new Vector();
    int index = 0, count = 0;
    while (ren.hasNextElement()) {
        int id = ren.nextRecordId();
        if (index++ < from) continue;
        if (count++ >= maxSize) break;
        Serializable ser = readObject(id, cls);
        if (ser != null) v.addElement(ser);
    }
    return v;
}

附件管理 (Lazy 物件專用)


// 批次插入附件
private static boolean insertAttachDatas(Lazy lazy) {
    RecordStore rs = getRecordStore(lazy.getNameOfAttachRMS());
    for (Object key : lazy.getAttachKeys()) {
        byte[] data = lazy.getAttach(key);
        int id = rs.addRecord(data, 0, data.length);
        lazy.savedAttach(key, id); // 回填附件 ID
    }
    return true;
}

// 按需載入附件 (由實作 Lazy 的物件內部調用)
public static byte[] getAttachData(Lazy lazy, int id) {
    return getRecordStore(lazy.getNameOfAttachRMS()).getRecord(id);
}

使用範例:聯絡人管理


// 1. 定義實體類別實作 Serializable
class Contact implements Serializable {
    int id = -1; String name, phone;
    public byte[] serialize() { 
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        dos.writeUTF(name); dos.writeUTF(phone);
        return bos.toByteArray();
    }
    public void unSerialize(byte[] data) {
        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
        name = dis.readUTF(); phone = dis.readUTF();
    }
    // getId/setId 省略
}

// 2. 定義含大頭照的聯絡人實作 Lazy
class RichContact implements Lazy {
    Contact baseInfo; // 主體
    byte[] photo;     // 附件
    // 實作 Serializable 方法序列化 baseInfo
    // 實作 Lazy 方法:
    public Object[] getAttachKeys() { return new Object[] {"photo"}; }
    public byte[] getAttach(Object key) { return "photo".equals(key) ? photo : null; }
    public void savedAttach(Object key, int id) { this.photoId = id; }
    public String getNameOfAttachRMS() { return "ContactPhotos"; }
}

// 3. 業務邏輯使用
RMSUtil.insertObject(new Contact("張三", "1380000"));
Contact c = (Contact) RMSUtil.readObject(1, Contact.class);
RMSUtil.updateObject(c);
Vector page = RMSUtil.listObjects(Contact.class, 0, 20); // 分頁查詢

框架特色與設計哲學

  • 零反射、零註解:純介面契約,相容所有 Java ME 實作 (CLDC 1.1+)
  • 輕量無侵入:僅 4 個檔案,無第三方依賴 (除可選 Log 框架)
  • 開發者掌控序列化:手寫 serialize/unSerialize 雖稍繁瑣,但效能最高、格式穩定、易除錯
  • 大物件分離:Lazy 機制解決 RMS 記錄大小限制 (通常 64KB) 與載入效能問題
  • 生命週期管理:closeAllRMS() 確保資源正確釋放

對比動態位元碼注入方案

市面有編譯期自動生成序列化程式碼的框架 (如動態插入 serialize 方法)。本框架刻意不採用:

  1. 方法名衝突風險:若類別原有同名方法會導致不可預期行為
  2. 黑盒風險:不知框架生成了什麼程式碼,除錯困難
  3. 過度封裝:持久化邏輯屬於核心業務,應由開發者明確掌控

封裝有度,核心邏輯親寫,心中才有底。

結語

此框架在多個商用 Java ME 專案中驗證穩定。原始碼開放,歡迎根據專案需求裁剪擴展 (如加入索引、加密、同步機制)。Java ME 雖老舊,但在特定物聯網/功能機領域仍有生存空間,良好的持久化抽象能大幅提升開發效率。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
元件類型職責
Serializable介面定義物件自行序列化/反序列化契約 (對應 Java SE 的 Serializable)
Lazy介面繼承 Serializable,支援大型物件延遲載入 (分離主體與附件)
RecordFetcher介面繼承 RecordComparator/RecordFilter,封裝查詢條件 (分頁、過濾、排序)
RMSUtil工具類核心操作入口:CRUD、查詢、快取管理、生命週期控制