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) 并非用于行到对象的映射,而是针对返回单列的查询。例如,获取 IDNAME 值列表:

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 表中查询了 NAMEMAJOR 字段。结果,数据库中的每一行都变成了一个 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 对象

现已理解 JdbcTemplatequeryForList() 方法正确用法,但尚未实现目标——将每行数据转为 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