Spring JDBC 异常:“IncorrectResultSetColumnCountException: Incorrect column count”
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 等其他方法省略。。。
}
为简化说明,此处省略 Spring 数据源等相关配置。
现需使用 JdbcTemplate
查询 STUDENT_TBL
表数据,并将每条记录转换为 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));
这是搭配 Class
参数使用 queryForList()
的常见误区。接下来我们将分析调用 queryForList()
方法出现 IncorrectResultSetColumnCountException
异常原因,并探讨正确实现方式。
3、queryForList() 方法
要理解 queryForList()
抛出异常的原因,需明确该方法的功能定位。
JdbcTemplate
提供了两个 queryForList()
方法:
List<T> queryForList(String sql, Class<T> elementType)
List<Map<String, Object>> queryForList(String sql)
接下来我们详细分析这两种方法。
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
由于 queryForList(String sql, Class<T> elementType)
仅适用于单列查询,也就不难理解为何在多列查询时会抛出 IncorrectResultSetColumnCountException
异常了。
3.2、检索多列多行
另一个 queryForList()
方法只接受一个 sql
参数。它执行 SQL 查询并将结果作为 List<Map<String, Object>>
返回。每一行都表示为一个 Map<String, Object>
,其中列名是键。
示例如下:
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
对象并获取 List
。
应改用 query()
方法配合 RowMapper
实现行到对象的映射。例如:使用内置的 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、总结
本文介绍了为什么 JdbcTemplate
中的 queryForList()
方法不适用于对象映射,以及如何使用 BeanPropertyRowMapper
将数据库中的行映射到自定义类。
Ref:https://www.baeldung.com/spring-jdbc-incorrectresultsetcolumncountexception