字符串的 Jdbc 模板查询:空结果数据访问异常:结果大小不正确:预期 1,实际 0

2022-08-31 08:52:33

我正在使用Jdbc模板从数据库中检索单个字符串值。这是我的方法。

    public String test() {
        String cert=null;
        String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN 
             where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
        cert = (String) jdbc.queryForObject(sql, String.class); 
        return cert;
    }

在我的场景中,完全有可能不会在我的查询上命中,所以我的问题是我如何绕过以下错误消息。

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

在我看来,我应该只取回一个 null,而不是抛出一个异常。我该如何解决这个问题?提前致谢。


答案 1

在 JdbcTemplate 中,,所有这些方法都期望执行的查询将返回一行且仅返回一行。如果未获得任何行或多于一行,则会导致 .现在,正确的方法是不捕获此异常 或 ,但请确保您使用的查询应仅返回一行。如果根本不可能,则改用方法。queryForIntqueryForLongqueryForObjectIncorrectResultSizeDataAccessExceptionEmptyResultDataAccessExceptionquery

List<String> strLst = getJdbcTemplate().query(sql, new RowMapper<String>() {
    public String mapRow(ResultSet rs, int rowNum) throws SQLException {
        return rs.getString(1);
    }
});

if (strLst.isEmpty()) {
    return null;
} else if (strLst.size() == 1) { // list contains exactly 1 element
    return strLst.get(0);
} else { // list contains more than 1 element
         // either return 1st element or throw an exception
}

答案 2

您还可以使用 ResultSetExtractor 而不是 RowMapper。两者都像彼此一样简单,唯一的区别是你调用ResultSet.next()

public String test() {
    String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN "
                 + " where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
    return jdbc.query(sql, new ResultSetExtractor<String>() {
        @Override
        public String extractData(ResultSet rs) throws SQLException,
                                                       DataAccessException {
            return rs.next() ? rs.getString("ID_NMB_SRZ") : null;
        }
    });
}

ResultSetExtractor 具有额外的好处,即您可以处理返回多行或未返回任何行的所有情况。

更新:几年过去了,我有一些技巧可以分享。JdbcTemplate与java 8 lambdas配合得非常好,以下示例就是为之设计的,但你可以很容易地使用静态类来实现相同的目标。

虽然问题与简单类型有关,但这些示例可作为提取域对象的常见情况的指南。

首先。为简单起见,假设您有一个具有两个属性的帐户对象。您可能希望有一个 for this domain 对象。Account(Long id, String name)RowMapper

private static final RowMapper<Account> MAPPER_ACCOUNT =
        (rs, i) -> new Account(rs.getLong("ID"),
                               rs.getString("NAME"));

现在,您可以直接在方法中使用此映射器来映射查询中的域对象(是一个实例)。AccountjtJdbcTemplate

public List<Account> getAccounts() {
    return jt.query(SELECT_ACCOUNT, MAPPER_ACCOUNT);
}

很好,但是现在我们想要我们原来的问题,我们使用我的原始解决方案重用RowMapper来为我们执行映射。

public Account getAccount(long id) {
    return jt.query(
            SELECT_ACCOUNT,
            rs -> rs.next() ? MAPPER_ACCOUNT.mapRow(rs, 1) : null,
            id);
}

很好,但这是您可能并且希望重复的模式。因此,您可以创建一个泛型工厂方法来为任务创建新的 ResultSetExtractor

public static <T> ResultSetExtractor singletonExtractor(
        RowMapper<? extends T> mapper) {
    return rs -> rs.next() ? mapper.mapRow(rs, 1) : null;
}

创建 ResultSetExtractor 现在变得微不足道。

private static final ResultSetExtractor<Account> EXTRACTOR_ACCOUNT =
        singletonExtractor(MAPPER_ACCOUNT);

public Account getAccount(long id) {
    return jt.query(SELECT_ACCOUNT, EXTRACTOR_ACCOUNT, id);
}

我希望这有助于表明您现在可以非常轻松地以强大的方式组合各个部分,从而使您的域更简单。

更新 2:与可选值的可选值(而不是 null)结合使用。

public static <T> ResultSetExtractor<Optional<T>> singletonOptionalExtractor(
        RowMapper<? extends T> mapper) {
    return rs -> rs.next() ? Optional.of(mapper.mapRow(rs, 1)) : Optional.empty();
}

现在使用时可以具有以下特性:

private static final ResultSetExtractor<Optional<Double>> EXTRACTOR_DISCOUNT =
        singletonOptionalExtractor(MAPPER_DISCOUNT);

public double getDiscount(long accountId) {
    return jt.query(SELECT_DISCOUNT, EXTRACTOR_DISCOUNT, accountId)
            .orElse(0.0);
}

推荐