1. 概述
使用Spring的JdbcTemplate
时,我们经常需要将查询结果转换为POJO列表。但一个常见陷阱是IncorrectResultSetColumnCountException
异常,这通常发生在误用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 ...
}
现在需要用JdbcTemplate
查询数据并转换为List<Student>
。查看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
参数组合使用的典型陷阱。接下来分析原因并给出解决方案。
3. queryForList()方法详解
要理解异常原因,必须先搞清楚queryForList()
的两个重载方法:
queryForList(String sql, Class<T> elementType)
→ 返回List<T>
queryForList(String sql)
→ 返回List<Map<String, Object>>
3.1. 检索单列数据为值列表
queryForList(String sql, Class<T> elementType)
并非用于行映射,而是专门处理单列查询。例如获取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
⚠️ 该方法仅支持单列查询,多列查询必然抛出IncorrectResultSetColumnCountException
。
3.2. 检索多列数据为Map列表
另一个重载方法queryForList(String 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);
查询所有列时同样适用:
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);
✅ 这种方式无需创建自定义类,适合快速获取多列数据。
4. 将行映射为Student对象
现在回到原始目标:将每行数据转换为Student
对象。正确做法是使用query()
方法配合RowMapper
,**推荐使用内置的BeanPropertyRowMapper
**:
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
通过自动匹配数据库列名与Java属性名,省去了手写RowMapper
的麻烦,是列名与属性名一致时的最佳选择。
5. 总结
本文揭示了queryForList()
的常见误用场景:
- ❌
queryForList(sql, Class)
仅适用于单列查询 - ✅ 多列查询应使用
queryForList(sql)
获取Map列表 - ✅ 映射到POJO需使用
query()
+BeanPropertyRowMapper
完整示例代码可在GitHub仓库获取。