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. 结合 RowMapper
的 query()
方法
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. 结合 ResultSetExtractor
的 query()
方法
ResultSetExtractor
的 extractData()
方法仅调用一次,用于处理所有行数据。示例实现:
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. 结合 RowCallbackHandler
的 query()
方法
使用 RowCountCallbackHandler
(RowCallbackHandler
的子类)统计行数:
已弃用方法:
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. 结合 RowMapper
的 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());
}
推荐替代方案:
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 获取。