1. 概述

本教程将探讨 JdbcTemplate 中已弃用的 queryForObject()query() 方法及其推荐替代方案。核心区别在于参数传递方式:已弃用方法使用 Object[] 数组传递参数,而新方法采用 Varargs(可变参数) 机制。这种设计更符合直觉,因为 Varargs 专为高效传递可变数量参数而设计。

所有示例均基于内存数据库 H2 中的 student 表进行演示,通过实际查询操作对比新旧方法差异。

2. 数据库准备

在分析具体方法前,先统一说明示例使用的 student 表结构:

CREATE TABLE student (
    student_id INT AUTO_INCREMENT PRIMARY KEY,
    student_name VARCHAR(255) NOT NULL,
    age INT,
    grade INT NOT NULL,
    gender VARCHAR(10) NOT NULL,
    state VARCHAR(100) NOT NULL
);
-- Student 1
INSERT INTO student (student_name, age, grade, gender, state) VALUES ('John Smith', 18, 3, 'Male', 'California');

-- Student 2
INSERT INTO student (student_name, age, grade, gender, state) VALUES ('Emily Johnson', 17, 2, 'Female', 'New York');

--More insert statements...

3. 已弃用的 query() 方法

JdbcTemplate 提供三种 query() 变体,分别使用不同函数式接口处理查询结果:

  • ResultSetExtractor
  • RowCallbackHandler
  • RowMapper

下面逐一分析其替代方案。

3.1. 结合 RowMapperquery() 方法

query() 方法返回数据库查询结果的行集合。先看按年龄和性别查询学生的示例:

public List<Student> getStudentsOfAgeAndGender(Integer age, String gender) {
    String sql = "select student_id, student_name, age, gender, grade from student where age= ? and gender = ?";
    Object[] args = {age, gender};
    return jdbcTemplate.query(sql, args, new StudentRowMapper());
}

⚠️ 痛点:即使只有一个参数,也必须创建 Object[] 数组,非常繁琐。

若需增加年级筛选条件,需修改方法:

public List<Student> getStudentsOfAgeGenderAndGrade(Integer age, String gender, Integer grade) {
    String sql = "select student_id, student_name, age, gender, grade from student where age= ? and gender = ? and grade = ?";
    Object[] args = {age, gender, grade};
    return jdbcTemplate.query(sql, args, new StudentRowMapper());
}

优化方案:使用 Varargs 版本消除样板代码:

public List<Student> getStudentsOfAgeGenderAndGrade(Integer age, String gender, String grade) {
    String sql = "select student_id, student_name, age, gender, grade from student where age= ? and gender = ? and grade = ?";
    return jdbcTemplate.query(sql, new StudentRowMapper(), age, gender, grade);
}

测试对比

@Test
public void givenDeprecatedMethodQuery_whenArgsAgeAndGender_thenReturnStudents() {
    List<Student> students = studentDaoWithDeprecatedJdbcTemplateMethods.getStudentsOfAgeGenderAndGrade(4, "Female", 2);
    for (Student student: students) {
        logger.info("Student Name: " + student.getStudentName() + " Student gender: " + student.getStudentGender());
    }
    assertEquals(5, students.size());
}
@Test
public void givenPreferredMethodQuery_whenArgsAgeAndGender_thenReturnStudents() {
    List<Student> students = studentDaoWithPreferredJdbcTemplateMethods.getStudentsOfAgeGenderAndGrade(4, "Female", 2);

    for (Student student: students) {
        logger.info("Student Name: " + student.getStudentName() + " Student gender: " + student.getStudentGender());
    }
    assertEquals(5, students.size());
}

输出结果一致

Student Name: Olivia Garcia Student gender: Female Student grade: 2
Student Name: Ava Davis Student gender: Female Student grade: 2
Student Name: Olivia Johnson Student gender: Female Student grade: 2
Student Name: Isabella Davis Student gender: Female Student grade: 2
Student Name: Sophia Hernandez Student gender: Female Student grade: 2

后续章节将聚焦方法使用差异,Varargs 优势不再重复说明。

3.2. 结合 ResultSetExtractorquery() 方法

ResultSetExtractorextractData() 方法仅调用一次,用于处理所有行数据。示例实现:

public class StudentResultExtractor implements ResultSetExtractor<List<Student>> {
    @Override
    public List<Student> extractData(ResultSet rs) throws SQLException {
        List<Student> students = new ArrayList<Student>();
        while(rs.next()) {
            Student student = new Student();
            student.setStudentId(rs.getInt("student_id"));
            student.setStudentName(rs.getString("student_name"));
            student.setAge(rs.getInt("age"));
            student.setStudentGender(rs.getString("gender"));
            student.setGrade(rs.getInt("grade"));
            student.setState(rs.getString("state"));
            students.add(student);
        }
        return students;
    }
}

已弃用方法

public List<Student> getStudentsOfGradeAndState(Integer grade, String state) {
    String sql = "select student_id, student_name, age, gender, grade, state from student where grade = ? and state = ?";
    Object[] args = {grade, state};
    return jdbcTemplate.query(sql, args, new StudentResultExtractor());
}

推荐替代方案

public List<Student> getStudentsOfGradeAndState(Integer grade, String state) {
    String sql = "select student_id, student_name, age, gender, grade, state from student where grade = ? and state = ?";
    return jdbcTemplate.query(sql, new StudentResultExtractor(), grade, state);
}

测试验证

@Test
public void givenDeprecatedMethodQuery_whenArgsGradeAndState_thenReturnStudents() {
    List<Student> students = studentDaoWithDeprecatedJdbcTemplateMethods.getStudentsOfGradeAndState(1, "New York");
    for (Student student: students) {
        logger.info("Student Name: " + student.getStudentName()
          + " Student grade: " + student.getStudentGender()
          + " Student State: " + student.getState());
    }
    assertEquals(6, students.size());
}
@Test
public void givenPreferredMethodQuery_whenArgsGradeAndState_thenReturnStudents() {
    List<Student> students = studentDaoWithPreferredJdbcTemplateMethods.getStudentsOfGradeAndState(1, "New York");
    for (Student student: students) {
        logger.info("Student Name: " + student.getStudentName()
          + " Student grade: " + student.getStudentGender()
          + " Student State: " + student.getState());
    }
    assertEquals(6, students.size());
}

输出结果

Student Name: Ethan Rodriguez Student grade: Male Student State: New York
Student Name: Benjamin Brown Student grade: Male Student State: New York
Student Name: Matthew Martinez Student grade: Male Student State: New York
Student Name: Christopher Lee Student grade: Male Student State: New York
Student Name: Liam Johnson Student grade: Male Student State: New York
Student Name: Mason Smith Student grade: Male Student State: New York

3.3. 结合 RowCallbackHandlerquery() 方法

使用 RowCountCallbackHandlerRowCallbackHandler 的子类)统计行数:

已弃用方法

public Integer getCountOfStudentsInAGradeFromAState(String grade, String state) {
    String sql = "select student_id, student_name, age, gender, grade, state from student where grade = ? and state = ?";
    Object[] args = {grade, state};
    RowCountCallbackHandler countCallbackHandler = new RowCountCallbackHandler();
    jdbcTemplate.query(sql, args, countCallbackHandler);
    return countCallbackHandler.getRowCount();
}

推荐替代方案

public Integer getCountOfStudentsInAGradeFromAState(String grade, String state) {
    String sql = "select student_id, student_name, age, gender, grade, state from student where grade = ? and state = ?";

    RowCountCallbackHandler countCallbackHandler = new RowCountCallbackHandler();
    jdbcTemplate.query(sql, countCallbackHandler, grade, state);
    return countCallbackHandler.getRowCount();
}

测试对比

@Test
public void givenDeprecatedMethodQuery_whenArgsGradeAndState_thenReturnCount() {
    Integer count = studentDaoWithDeprecatedJdbcTemplateMethods.getCountOfStudentsInAGradeFromAState(1, "New York");
    logger.info("Total students of grade 1 from New York:" + count);
    assertEquals(6, count);
}

@Test
public void givenPreferredMethodQuery_whenArgsGradeAndState_thenReturnCount() {
    Integer count = studentDaoWithPreferredJdbcTemplateMethods.getCountOfStudentsInAGradeFromAState(1, "New York");
    logger.info("Total students of grade 1 from New York:" + count);
    assertEquals(6, count);
}

输出结果

Total students of grade 1 from New York: 6

4. 已弃用的 queryForObject() 方法

queryForObject() 有两个使用 Object[] 传参的已弃用变体:

  • queryForObject(String sql, Object[] args, Class<T> requiredType)
  • queryForObject(String sql, Object[] args, RowMapper<T> rowMapper)

4.1. 结合 RowMapperqueryForObject() 方法

queryForObject() 专用于处理单行查询结果

已弃用方法

public Student getStudentOfStudentIDAndGrade(Integer studentID, Integer grade) {
    String sql = "select student_id, student_name, age, gender, grade from student where student_id = ? and grade = ?";
    Object[] args = {studentID, grade};

    return jdbcTemplate.queryForObject(sql, args, new StudentRowMapper());
}

推荐替代方案

public Student getStudentOfStudentIDAndGrade(Integer studentID, Integer grade) {
    String sql = "select student_id, student_name, age, gender, grade from student where student_id = ? and grade = ?";

    return jdbcTemplate.queryForObject(sql, new StudentRowMapper(), studentID, grade);
}

测试验证

@Test
public void givenDeprecatedMethodQueryForObject_whenArgsStudentIDAndGrade_thenReturnStudent() {
    Student student = studentDaoWithDeprecatedJdbcTemplateMethods.getStudentOfStudentIDAndGrade(4, 1);
    assertEquals(1, student.getGrade());
    assertEquals(4, student.getStudentId());
    logger.info("Student ID: " + student.getStudentId()
      + " Student Name: " + student.getStudentName() + " Student grade: " + student.getGrade());
}

@Test
public void givenPreferredMethodQueryForObject_whenArgsStudentIDAndGrade_thenReturnStudent() {
    Student student = studentDaoWithPreferredJdbcTemplateMethods.getStudentOfStudentIDAndGrade(4, 1);
    assertEquals(1, student.getGrade());
    assertEquals(4, student.getStudentId());
    logger.info("Student ID: " + student.getStudentId()
      + " Student Name: " + student.getStudentName() + " Student grade: " + student.getGrade());
}

输出结果

Student ID: 4 Student Name: Sophia Martinez Student grade: 1

4.2. 结合 Class<T>queryForObject() 方法

已弃用方法

public Integer getCountOfGenderInAGrade(String gender, Integer grade) {
    String sql = "select count(1) as total from student where gender = ? and grade = ?";
    Object[] args = {gender, grade};

    return jdbcTemplate.queryForObject(sql, args, Integer.class);
}

推荐替代方案

public Integer getCountOfGenderInAGrade(String gender, Integer grade) {
    String sql = "select count(1) as total from student where gender = ? and grade = ?";

    return jdbcTemplate.queryForObject(sql, Integer.class, gender, grade);
}

测试对比

@Test
public void givenPreferredMethodQueryForObject_whenArgsGenderAndGrade_thenReturnCount() {
    Integer count = studentDaoWithPreferredJdbcTemplateMethods.getCountOfGenderInAGrade("Female", 2);
    assertEquals(6, count);
    logger.info("Total number of Female Students: " + count);
}

@Test
public void givenDeprecatedMethodQueryForObject_whenArgsGenderAndGrade_thenReturnCount() {
    Integer count = studentDaoWithPreferredJdbcTemplateMethods.getCountOfGenderInAGrade("Female", 2);
    assertEquals(6, count);
    logger.info("Total number of Female Students: " + count);
}

输出结果

Total number of Female Students: 6

5. 总结

本教程深入分析了 JdbcTemplate 中已弃用的 query()queryForObject() 方法及其替代方案。通过实际示例展示了 Varargs 参数传递机制如何消除 Object[] 数组的样板代码,使代码更简洁直观。建议在 Spring Boot 2.4.X 及以上版本中优先使用新方法,提升代码可维护性。

完整示例代码可在 GitHub 获取。


原始标题:How to Replace Deprecated jdbcTemplate.queryForObject and jdbcTemplate.query in Spring Boot 2.4.X and above