1. 概述
在这个简短教程中,我们将探讨Spring的JdbcTemplate
中出现的异常EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0
。
首先,我们将详细分析这个异常的根本原因。然后,通过一个实际示例来演示如何重现它,并学习如何解决这个问题。
2. 原因
Spring的JdbcTemplate
类提供了执行SQL查询并获取结果的便捷方式。它底层使用了JDBC API。
通常情况下,当预期至少会返回一行结果,但实际返回的是0行时,JdbcTemplate
会抛出EmptyResultDataAccessException
。
现在我们了解了异常含义,接下来将通过实例来重现并修复它。
3. 重现异常
例如,考虑Employee
类:
public class Employee {
private int id;
private String firstName;
private String lastName;
public Employee(int id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
// standard getters and setters
}
接着,我们创建一个使用JdbcTemplate
处理SQL查询的数据访问对象(DAO)类:
public class EmployeeDAO {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
}
值得注意的是,因为JdbcTemplate
需要一个DataSource
对象,我们在setter方法中注入了它。
现在,我们将添加一个方法,根据id
获取Employee
对象。我们可以使用JdbcTemplate
提供的queryForObject()
方法:
public Employee getEmployeeById(int id) {
RowMapper<Employee> employeeRowMapper = (rs, rowNum) -> new Employee(rs.getInt("ID"), rs.getString("FIRST_NAME"), rs.getString("LAST_NAME"));
return jdbcTemplate.queryForObject("SELECT * FROM EMPLOYEE WHERE id=?", employeeRowMapper, id);
}
如你所见,第一个参数是SQL查询,第二个参数是将ResultSet
映射为Employee
对象的RowMapper
。
实际上,queryForObject()
期望返回恰好一行数据。因此,如果传递的id
不存在,它就会直接抛出EmptyResultDataAccessException
。
让我们用一个测试来验证这一点:
@Test(expected = EmptyResultDataAccessException.class)
public void whenIdNotExist_thenThrowEmptyResultDataAccessException() {
EmployeeDAO employeeDAO = new EmployeeDAO();
ReflectionTestUtils.setField(employeeDAO, "jdbcTemplate", jdbcTemplate);
Mockito.when(jdbcTemplate.queryForObject(anyString(), ArgumentMatchers.<RowMapper<Employee>> any(), anyInt()))
.thenThrow(EmptyResultDataAccessException.class);
employeeDAO.getEmployeeById(1);
}
由于没有对应给定id
的员工,指定的查询返回了0行数据。因此,JdbcTemplate
会因EmptyResultDataAccessException
而失败。
4. 解决异常
最简单的解决方案是捕获异常并返回null
:
public Employee getEmployeeByIdV2(int id) {
RowMapper<Employee> employeeRowMapper = (rs, rowNum) -> new Employee(rs.getInt("ID"), rs.getString("FIRST_NAME"), rs.getString("LAST_NAME"));
try {
return jdbcTemplate.queryForObject("SELECT * FROM EMPLOYEE WHERE id=?", employeeRowMapper, id);
} catch (EmptyResultDataAccessException e) {
return null;
}
}
这样,当SQL查询的结果为空时,我们确保返回null
。
现在,让我们添加另一个测试用例以确认一切按预期工作:
@Test
public void whenIdNotExist_thenReturnNull() {
EmployeeDAO employeeDAO = new EmployeeDAO();
ReflectionTestUtils.setField(employeeDAO, "jdbcTemplate", jdbcTemplate);
Mockito.when(jdbcTemplate.queryForObject(anyString(), ArgumentMatchers.<RowMapper<Employee>> any(), anyInt()))
.thenReturn(null);
assertNull(employeeDAO.getEmployeeByIdV2(1));
}
5. 总结
在这篇简短教程中,我们详细讨论了JdbcTemplate
抛出"EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0"
异常的原因。
在过程中,我们看到了如何产生这个异常,以及如何通过实际示例来解决它。
如往常一样,所有示例的完整源代码可以在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-jdbc。