ResultSet 核心概念
ResultSet 封裝 SQL 查詢結果集,維護一個游標指向當前資料列。初始位於第一列之前,呼叫 next() 移動到下一列並返回 true,直到結束返回 false。
基本遍歷模式
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, name, salary FROM employees")) {
while (rs.next()) {
int id = rs.getInt("id"); // 或 rs.getInt(1)
String name = rs.getString("name"); // 建議用欄位名,可讀性高
double salary = rs.getDouble("salary");
System.out.printf("ID: %d, Name: %s, Salary: %.2f%n", id, name, salary);
}
}
游標與併發型態 (JDBC 2.0+)
| 類型常數 | 游標移動性 | 可更新性 | 適用場景 |
TYPE_FORWARD_ONLY | 僅向前 | 唯讀 | 預設、效能最好、串流處理大量資料 |
TYPE_SCROLL_INSENSITIVE | 雙向捲動 | 唯讀 | 需隨機存取、不需看見他人修改 |
TYPE_SCROLL_SENSITIVE | 雙向捲動 | 可更新 | 需即時反映他人修改、開銷大 |
// 建立可捲動、可更新的 ResultSet
Statement stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE
);
欄位存取:索引 vs 名稱
- 索引 (1-based):效能稍佳、適用
SELECT * 或衍生欄位無名稱情況。
- 名稱:可讀性高、不怕欄位順序變更、不區分大小寫。
- 重複欄位名:
getString("name") 只返回第一個匹配,必須用索引區分。
JDBC 資料類型映射完整對照表 (核心參考)
X = 推薦、x = 合法但不推薦、空白 = 不支援
| JDBC Type | getByte | getShort | getInt | getLong | getFloat | getDouble | getBigDecimal | getBoolean | getString | getBytes | getDate | getTime | getTimestamp | getAsciiStream | getUnicodeStream | getBinaryStream | getObject |
| BIT | x | x | x | x | x | x | x | X | x | | | | | | | | x |
| TINYINT | X | x | x | x | x | x | x | x | x | | | | | | | x |
| SMALLINT | x | X | x | x | x | x | x | x | x | | | | | | | x |
| INTEGER | x | x | X | x | x | x | x | x | x | | | | | | | x |
| BIGINT | x | x | x | X | x | x | x | x | x | | | | | | | x |
| REAL | x | x | x | x | X | x | x | x | x | | | | | | | x |
| FLOAT/DOUBLE | x | x | x | x | x | X | x | x | x | | | | | | | x |
| DECIMAL/NUMERIC | x | x | x | x | x | x | X | x | x | | | | | | | x |
| CHAR/VARCHAR/LONGVARCHAR | x | x | x | x | x | x | x | x | X | x | | | | X | X | | x |
| BINARY/VARBINARY/LONGVARBINARY | | | | | | | | | x | X | | | | | | X | x |
| DATE | | | | | | | | | x | | X | x | x | | | | x |
| TIME | | | | | | | | | x | | x | X | x | | | | x |
| TIMESTAMP | | | | | | | | | x | | x | x | X | | | | x |
大型資料流式讀取 (避免記憶體溢位)
getBinaryStream():原始位元組流 (BLOB, LONGVARBINARY)。
getAsciiStream():單位元組字符流 (CLOB, LONGVARCHAR)。
getCharacterStream() (JDBC 2.0+):字符流 (取代 getUnicodeStream)。
// 4KB 緩衝區分塊讀取 CLOB
try (InputStream in = rs.getAsciiStream("content");
OutputStream out = new FileOutputStream("dump.txt")) {
byte[] buf = new byte[4096];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
}
// 注意:流必須立即讀取,下一次 getXXX 呼叫會自動關閉前一個流!
NULL 值處理:wasNull() 陷阱
int value = rs.getInt("nullable_column");
if (rs.wasNull()) {
// 資料庫是 NULL,getInt 返回 0
value = -1; // 業務預設值
}
// 物件類型 getString/getObject 返回 null 直接判斷即可
String name = rs.getString("name");
if (name == null) { /* 是 NULL */ }
| getXXX 類型 | 資料庫 NULL 返回值 | wasNull() 返回 |
| 物件類型 (String, BigDecimal, Date, ...) | null | true |
| 基本數值類型 (byte, short, int, long, float, double) | 0 | true |
| boolean | false | true |
多結果集處理 (預存程序/批次 SQL)
boolean hasResults = stmt.execute(unknownSql); // 可能返回多結果集/更新計數
while (true) {
int updateCount = stmt.getUpdateCount();
if (updateCount > 0) {
System.out.println("Rows affected: " + updateCount);
stmt.getMoreResults(); // 檢查下一個結果
continue;
}
if (updateCount == 0) { // DDL 或 0 筆更新
stmt.getMoreResults();
continue;
}
// updateCount == -1 表示可能是 ResultSet
ResultSet rs = stmt.getResultSet();
if (rs != null) {
// 處理 ResultSet...
while (rs.next()) { /* ... */ }
stmt.getMoreResults();
continue;
}
// 兩者皆無 → 結束
break;
}
資源管理鐵律
ResultSet、Statement、Connection 必須在 finally 區塊或 try-with-resources 中關閉。
- 關閉
Statement 會自動關閉其產生的 ResultSet。
- 連線池環境下,
conn.close() 是歸還連線而非物理斷開。