問題根源:型別系統不匹配
- 關聯式資料庫:任何型別皆可為 NULL (代表未知/缺值)
- Java 基本型別 (
int, long, double...):**不可為 null**,只有物件參考可為 null
- JDBC
ResultSet.getInt() 等基本型別取值方法:若資料庫欄位為 NULL,回傳值**由驅動程式決定** (可能是 0、預設值、上一筆值...),行為不可預期
標準解決模式:wasNull() 檢查
int idx = rs.getInt(1); // 1. 先取值
if (rs.wasNull()) { // 2. 立即檢查上一次 getXxx 是否為 NULL
idx = -1; // 3. 套用應用層預設值 (如 -1 表「無資料」)
}
關鍵規則
- 順序固定:必須在同一個
getXxx() 呼叫緊接著呼叫 wasNull(),中間不可插入其他 JDBC 呼叫
- 僅適用基本型別 getter:
getInt/getLong/getDouble/getFloat/getBoolean/getByte/getShort;物件型別 getString(), getObject() 直接回傳 null 參考,無需 wasNull()
- 驅動程式相容性:JDBC 規範要求所有標準驅動支援,但舊版/第三方驅動偶有 Bug,建議單元測試驗證
完整範例:安全讀取多種型別
public class SafeResultSetReader {
public static Integer getIntOrNull(ResultSet rs, int col) throws SQLException {
int val = rs.getInt(col);
return rs.wasNull() ? null : val;
}
public static Long getLongOrNull(ResultSet rs, int col) throws SQLException {
long val = rs.getLong(col);
return rs.wasNull() ? null : val;
}
public static Double getDoubleOrNull(ResultSet rs, int col) throws SQLException {
double val = rs.getDouble(col);
return rs.wasNull() ? null : val;
}
// 使用範例
public void process(ResultSet rs) throws SQLException {
Integer salary = getIntOrNull(rs, 3);
if (salary != null) {
System.out.println("薪資: " + salary);
} else {
System.out.println("薪資欄位為 NULL");
}
}
}
常見錯誤模式
| 錯誤代碼 | 問題 | 正確做法 |
if (rs.getInt(1) == 0) ... | 無法區分「真實 0」與「NULL 轉 0」 | 用 wasNull() 區分 |
int v = rs.getInt(1); ... other code ... if (rs.wasNull()) | 中間插入其他 JDBC 呼叫導致 wasNull() 狀態重置 | 緊接著檢查 |
String s = rs.getString(1); if (rs.wasNull()) | getString 回傳 null 參考,wasNull() 永遠 false | 直接 if (s == null) |
現代框架處理
- JPA/Hibernate:基本型別欄位建議用包裝型別 (
Integer, Long) 直接映射 NULL → null
- MyBatis:
resultMap 指定 javaType="Integer" 自動處理
- Spring JdbcTemplate:
queryForObject(sql, Integer.class) 自動轉換 NULL → null (需注意基本型別會拋異常)
最佳實務總結
- 資料庫欄位允許 NULL 時,Java 端對應欄位**優先使用包裝型別**
- 若必須用基本型別,嚴格遵循
getXxx() → wasNull() → 套用預設值 三步驟
- 建立通用工具方法 (
SafeResultSetReader) 統一處理,避免分散重複邏輯
- 單元測試覆蓋 NULL/非 NULL 兩種情境