解決 Spring JDBC“IncorrectResultSetColumnCountException:列數不正確”
1.概述
在使用 Spring 的JdbcTemplate,
我們經常需要將查詢結果轉換為 POJO List
。然而,我們常常會遇到一個陷阱,那就是IncorrectResultSetColumnCountException.
這通常發生在我們誤用JdbcTemplate
的queryForList()
方法時,尤其是當我們嘗試將其直接對應到自訂 POJO 類別時。
在本教程中,我們將探討導致這種情況的原因、如何正確使用queryForList()
以及如何將查詢結果對應到自訂類別。
2.問題介紹
假設我們有一個STUDENT_TBL
資料庫表,其中包含四名學生的資料:
CREATE TABLE STUDENT_TBL
(
ID int NOT NULL PRIMARY KEY,
NAME varchar(255),
MAJOR varchar(255)
);
INSERT INTO STUDENT_TBL VALUES (1, 'Kai', 'Computer Science');
INSERT INTO STUDENT_TBL VALUES (2, 'Eric', 'Computer Science');
INSERT INTO STUDENT_TBL VALUES (3, 'Kevin', 'Banking');
INSERT INTO STUDENT_TBL VALUES (4, 'Liam', 'Law');
此外,我們也建立了一個Student
POJO 類別:
public class Student {
private Integer id;
private String name;
private String major;
public Student() {
}
public Student(Integer id, String name, String major) {
this.id = id;
this.name = name;
this.major = major;
}
// getter, setter, equals, and hashCode methods are omitted ...
}
為了簡單起見,我們將跳過顯示相關的 Spring 配置,例如資料來源配置。
現在,我們要使用JdbcTemplate
從STUDENT_TBL
表中查詢數據 並將每個條目轉換為Student
物件以獲得Student
物件List
。
在查看了JdbcTemplate
API 之後, queryForList ()
似乎可以處理此任務,正如其名稱所示。因此,我們可能會想到類似這樣的方法:
List<Student> students = jdbcTemplate.queryForList("SELECT * FROM STUDENT_TBL", Student.class);
然而,當我們測試它時,這一行拋出了異常:
assertThrows(IncorrectResultSetColumnCountException.class, () -> jdbcTemplate.queryForList("SELECT * FROM STUDENT_TBL", Student.class));
這是將queryForList()
與Class.
接下來,讓我們理解為什麼queryForList()
會拋出IncorrectResultSetColumnCountException
,並探索實現目標的正確方法。
3. queryForList()
方法
要理解為什麼queryForList()
會拋出異常,我們必須了解這個方法的作用。
JdbcTemplate
提供兩種queryForList()
方法:
-
queryForList(String sql, Class<T> elementType)
回傳List<T>
-
queryForList(String sql)
回傳List<Map<String, Object>>
接下來我們來仔細看看它們。
3.1 從資料庫中檢索單一列作為值List
queryForList(String sql, Class<T> elementType)
並非用於將行對應到物件。相反,它適用於返回單列的查詢。例如,在我們的範例中,我們可以使用 if 來取得ID
或NAME
值的清單:
List<String> names = jdbcTemplate.queryForList("SELECT NAME FROM STUDENT_TBL", String.class);
assertEquals(List.of("Kai", "Eric", "Kevin", "Liam"), names);
List<Integer> ids = jdbcTemplate.queryForList("SELECT ID FROM STUDENT_TBL", Integer.class);
assertEquals(List.of(1, 2, 3, 4), ids);
如上例所示,第二個參數Class<T> elementType
表示我們正在查詢的單列的類型:
-
NAME
–String.class
-
ID
–Integer.class
由於queryForList(String sql, Class<T> elementType)
僅用於單列查詢,因此不難理解為什麼當我們嘗試將其用於多列查詢時它會拋出IncorrectResultSetColumnCountException
。
3.2. 從資料庫中檢索多列的行作為Map
List
另一個queryForList()
僅接受一個sql
參數。它執行 SQL 查詢並以List<Map<String, Object>>
的形式傳回結果。每一行都表示為一個Map
,其中列名是鍵。
接下來我們來看幾個例子:
List<Map<String, Object>> nameMajorRowMaps = jdbcTemplate.queryForList("SELECT NAME, MAJOR FROM STUDENT_TBL");
assertEquals(List.of(
Map.of("NAME", "Kai", "MAJOR", "Computer Science"),
Map.of("NAME", "Eric", "MAJOR", "Computer Science"),
Map.of("NAME", "Kevin", "MAJOR", "Banking"),
Map.of("NAME", "Liam", "MAJOR", "Law")
), nameMajorRowMaps);
在此範例中,我們使用queryForList()
從STUDENT_TBL
表中查詢了NAME
和MAJOR
。結果,資料庫中的每一行都變成了一個Map
物件。
類似地,我們可以從表中選擇所有列並按名稱存取任何列:
List<Map<String, Object>> rowMaps = jdbcTemplate.queryForList("SELECT * FROM STUDENT_TBL");
assertEquals(List.of(
Map.of("ID", 1, "NAME", "Kai", "MAJOR", "Computer Science"),
Map.of("ID", 2, "NAME", "Eric", "MAJOR", "Computer Science"),
Map.of("ID", 3, "NAME", "Kevin", "MAJOR", "Banking"),
Map.of("ID", 4, "NAME", "Liam", "MAJOR", "Law")
), rowMaps);
我們可以看到, queryForList(sql)
提供了一種快速的方法,可以直接從資料庫中取得多列的行,而無需建立自訂類別。
4. 將每一行對應到Student
對象
現在我們已經了解了JdbcTemplate
的queryForList()
方法的正確用法。然而,我們還沒有達到我們的目標,因為我們的目標是將資料庫的每一行都轉換為Student
,並取得一個Student
物件的List
。
我們應該使用帶有RowMapper
的query()
方法將每一行映射到一個Student
物件。例如,我們可以使用方便的內建BeanPropertyRowMapper
類,該類別的工作原理是將資料庫中的列名與 Java 類別的屬性名稱進行比對:
List<Student> expected = List.of(
new Student(1, "Kai", "Computer Science"),
new Student(2, "Eric", "Computer Science"),
new Student(3, "Kevin", "Banking"),
new Student(4, "Liam", "Law")
);
List<Student> students = jdbcTemplate.query("SELECT * FROM STUDENT_TBL", new BeanPropertyRowMapper<>(Student.class));
assertEquals(expected, students);
如範例所示, BeanPropertyRowMapper
使我們免於編寫自訂RowMapper
程式碼,並且當列和欄位名稱匹配時非常理想。
5. 結論
在本文中,我們了解為什麼queryForList
() 不適用於物件映射,並探討如何使用BeanPropertyRowMapper
將資料庫中的一行映射到自訂類別。
與往常一樣,範例的完整原始程式碼可在 GitHub 上找到。