| 元件 | 類型 | 職責 |
Serializable | 介面 | 定義物件自行序列化/反序列化契約 (對應 Java SE 的 Serializable) |
Lazy | 介面 | 繼承 Serializable,支援大型物件延遲載入 (分離主體與附件) |
RecordFetcher | 介面 | 繼承 RecordComparator/RecordFilter,封裝查詢條件 (分頁、過濾、排序) |
RMSUtil | 工具類 | 核心操作入口:CRUD、查詢、快取管理、生命週期控制 |
核心介面設計
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 方法)。本框架刻意不採用:
- 方法名衝突風險:若類別原有同名方法會導致不可預期行為
- 黑盒風險:不知框架生成了什麼程式碼,除錯困難
- 過度封裝:持久化邏輯屬於核心業務,應由開發者明確掌控
封裝有度,核心邏輯親寫,心中才有底。
結語
此框架在多個商用 Java ME 專案中驗證穩定。原始碼開放,歡迎根據專案需求裁剪擴展 (如加入索引、加密、同步機制)。Java ME 雖老舊,但在特定物聯網/功能機領域仍有生存空間,良好的持久化抽象能大幅提升開發效率。
- 上一篇文章: spymemcached vs xmemcached 效能大比拚:簡單類型與複雜物件讀寫實測分析
- 下一篇文章: 構建高擴展性 Java 應用:多核時代的效能調優實戰指南
Copyright ©
程式碼之家 All Rights Reserved