JdbcTemplate 中废弃的 query(...) 和 queryForObject(...) 方法

1、概览

在 Spring Boot 2.4.x 以后,JdbcTemplate 中有几个方法注解了 @Deprecated,也就是说被废弃了:

  • query(String sql, @Nullable Object[] args, ResultSetExtractor<T> rse)
  • query(String sql, @Nullable Object[] args, RowCallbackHandler rch)
  • query(String sql, @Nullable Object[] args, RowMapper<T> rowMapper)
  • queryForObject(String sql, @Nullable Object[] args, RowMapper<T> rowMapper);
  • queryForObject(String sql, @Nullable Object[] args, Class<T> requiredType)
  • queryForList(String sql, @Nullable Object[] args, Class<T> elementType)

这些过时的方法都使用对象数组 Object[] args 传递参数。

JdbcTemplate 又提供了一些新的方法来代替它们,新方法使用了 “可变参数”,即 Varargs 传递参数。

本文接下来会讲解一下新旧方法的用法和区别。

2、数据库

使用 H2 内存数据库进行演示,假如我们有一张 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');

-- 其他 insert 语句 ...

3、废弃的 query() 方法

JdbcTemplate 中,有三种不同的 query() 方法,每种方法都使用不同的函数接口来收集或处理查询结果中的记录。如:ResultSetExtractorRowCallbackHandlerRowMapper

3.1、使用 RowMapper 参数的 query()

query() 方法返回 List,表示查询数据库返回的记录。

例如:根据 agegender 检索 student 表。

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[] 类型的变量 args 存储了查询参数。即使是单个查询参数,也需要添加到数组中,这很不方便。哪怕是只有一个参数,也要声明一个数组。

如果需要新增一个 grade 过滤参数的话,就比较麻烦,可能得重写声明一个方法:

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());
}

但是,如果使用“可变参数”版本的新方法,就比较灵活了,如下。

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);
}

有了可变参数,不需要声明 Object[] args 对象。

剩下的方法都是同理,就不详细解释了

3.2、使用 ResultSetExtractor 参数的 query() 方法

它需要一个实现了 ResultSetExtractor 接口的实现,用于把查询结果 ResultSet 封装为 List<T> 对象。

创建一个 StudentResultExtractor,如下:

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;
    }
}

废弃版本的用法,也就是使用 Object[] args 传递参数:

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);
}

3.3、使用 RowCallbackHandler 参数的 query() 方法

该方法,需要一个 RowCallbackHandler 接口的实现,作为查询结果的回调。

在这里我们使用 RowCountCallbackHandler 实现,用于统计结果行数。

废弃的版本 的用法如下:

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();
}

4、废弃的 queryForObject() 方法

queryForObject() 有两个已废弃的重载,均使用 Object[] 传递查询参数。

4.1、使用 RowMapper 参数的 queryForObject() 方法

query() 方法不同,queryForObject() 方法用于处理单行的数据库查询结果。

先看看下面这个已废弃版本 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());
}

如上,和废弃的 query 方法一样,也需要为查询参数声明一个 Object[] 类型的变量。

使用新的代替方法,如下:

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);
}

使用可变参数,更加简洁了。

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);
}

5、总结

JdbcTemplate 废弃了一些使用 Object[] 作为参数的 query(...)queryForObject(...) 方法,同时提供了新的代替方法。新方法使用 “可变参数”,使得传递 SQL 变量参数更加的简洁、优雅。


参考:https://www.baeldung.com/spring-boot-replace-deprecated-jdbctemplate-queryforobject-query