修复使用JdbcTemplate时的EmptyResultDataAccessException

2023/05/18

一、概述

在这个简短的教程中,我们将查看Spring JdbcTemplateEmptyResultDataAccessException:结果大小不正确:预期为 1,实际为 0 ”异常。

首先,我们将详细讨论此异常的根本原因。然后,我们将看到如何使用实际示例重现它,最后学习如何修复它。

2. 原因

Spring JdbcTemplate类提供了执行 SQL 查询和检索结果的便捷方式。它在底层使用JDBC API 。

通常,当预期结果至少有一行但实际返回零行时,*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*,因此指定的查询返回零行。结果,*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:结果大小不正确:预期为 1,实际为 0”的原因。

在此过程中,我们看到了如何产生异常以及如何使用实际示例修复它。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章