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()的两个重载方法:

  1. queryForList(String sql, Class<T> elementType) → 返回List<T>
  2. 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指定单列数据类型:

  • NAMEString.class
  • IDInteger.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仓库获取。


原始标题:Resolving Spring JDBC “IncorrectResultSetColumnCountException: Incorrect column count” | Baeldung