JDBC ResultSet 預設為**連線導向**,游標依賴底層 Connection 保持開啟。Windows 下某些驅動允許關閉連線後繼續讀取已緩衝資料,但 **Linux/Unix 驅動嚴格遵循規範,連線關閉即失效**。若直接在 JSP/Service 層傳遞 ResultSet,跨平台部署極易出錯。
populate(ResultSet) 將資料完整複製至記憶體,斷開連線後仍可序列化、傳遞、修改。acceptChanges() 重新連線寫回,自動偵測衝突。public class DBManager {
private String url, username, password;
// 原版:回傳 ResultSet (跨平台不穩)
// public ResultSet getResultSet(String sql) throws Exception { ... }
// 升級版:回傳 CachedRowSet (實作 ResultSet 介面)
public ResultSet getResultSet(String sql) throws Exception {
Connection conn = getConnection(); // 從連線池取得
try {
CachedRowSet crs = new CachedRowSetImpl(); // com.sun.rowset.CachedRowSetImpl (JDK 內建) 或 javax.sql.rowset.CachedRowSet (JDBC 4.0+)
crs.setUrl(url);
crs.setUsername(username);
crs.setPassword(password);
crs.setCommand(sql);
crs.execute(conn); // 內部自動 populate 並關閉連線
return crs; // 直接回傳,型別相容
} catch (Exception e) {
throw new Exception("獲取 CachedRowSet 失敗: " + e.getMessage() + "\nSQL: " + sql);
} finally {
freeConnection(conn); // 確保連線歸還池中
}
}
}
| 操作 | 說明 |
|---|---|
crs.execute(conn) | 執行查詢、填充資料、自動關閉傳入連線 (若為 pooled connection 需確認行為) |
crs.beforeFirst() | 游標重置至起始位置 (預設已在 beforeFirst) |
crs.next() | 逐筆讀取,與 ResultSet 完全一致 |
crs.updateString("col", "val") | 修改當前行資料 (需 crs.acceptChanges() 寫回) |
crs.acceptChanges() | 重新取得連線寫回資料庫,樂觀鎖失敗拋 SyncProviderException |
crs.release() | 釋放資源 (JDK 實作建議顯式呼叫) |
com.sun.rowset.CachedRowSetImpl (Sun 內建實作,非標準 API)。javax.sql.rowset.CachedRowSet 標準介面,配合 RowSetProvider.newFactory().createCachedRowSet() 取得實作。RowSetFactory、Spring CachedRowSetFactoryBean。OOM,應改用分頁查詢 (LIMIT/OFFSET 或游標)。acceptChanges() 預設比較所有欄位偵測衝突,大表可自訂 SyncProvider 僅比較版本欄位/時間戳。javax.sql.RowSet 或 ResultSet (介面)。ResultSet 介面方法 (無驅動專屬擴充)。CachedRowSet 以**零修改上層代碼**解決 ResultSet 跨平台離線難題,是舊系統 JDBC 層升級的最高性價比方案。新專案建議直接採用 Spring JdbcTemplate / MyBatis 等高層抽象,從架構層面避免裸露 ResultSet。