字符串的Jdbctemplate查询:EmptyResultDataAccessException:结果大小不正确:预期1,实际0

Byr*_*ron 99 java spring jdbctemplate

我正在使用Jdbctemplate从db中检索单个String值.这是我的方法.

    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;
    }
Run Code Online (Sandbox Code Playgroud)

在我的方案中,完全可能不会得到我的查询,所以我的问题是如何解决以下错误消息.

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0
Run Code Online (Sandbox Code Playgroud)

在我看来,我应该回到null而不是抛出异常.我怎样才能解决这个问题?提前致谢.

Rak*_*yal 167

在JdbcTemplate的,queryForInt,queryForLong,queryForObject所有这些方法预计执行的查询将返回一个只有一行.如果没有行或多行将导致IncorrectResultSizeDataAccessException.现在正确的方法不是捕获此异常,或者EmptyResultDataAccessException确保您使用的查询应该只返回一行.如果根本不可能,则使用query方法代替.

List<String> strLst  = getJdbcTemplate().query(sql,new RowMapper {

  public Object 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 elements
  //your wish, you can either throw the exception or return 1st element.    
}
Run Code Online (Sandbox Code Playgroud)

  • 匿名类定义中缺少括号 - new RowMapper**()** (3认同)
  • 嗨@Rakesh,为什么不在`catch中返回null`(EmptyResultDataAccessException异常){return null; }? (2认同)
  • “但请确保您使用的查询应仅返回一行”。这太疯狂了。这就像命令或强迫用户,而不应该在框架中处理。我认为这是框架中的一个差距,或者框架中没有很好地处理它。您无法决定我的查询应该是什么样子。如果我向框架提供查询,它应该处理基本的东西。毕竟这就是它的意义.. (2认同)

Bre*_*yan 42

您也可以使用a ResultSetExtractor而不是a 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;
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

ResultSetExtractor有,你可以处理那里有一排以上所有情况下,或者没有返回的行额外的好处.

更新:几年后,我有一些技巧可以分享.JdbcTemplate与java 8 lambdas完美配合,以下示例是为其设计的,但您可以非常轻松地使用静态类来实现相同的功能.

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

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

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

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

public List<Account> getAccounts() {
    return jt.query(SELECT_ACCOUNT, MAPPER_ACCOUNT);
}
Run Code Online (Sandbox Code Playgroud)

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

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

太棒了,但这是你可能并且希望重复的模式.因此,您可以创建一个通用工厂方法来ResultSetExtractor为任务创建新方法.

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

创建一个ResultSetExtractor现在变得微不足道.

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

public Account getAccount(long id) {
    return jt.query(SELECT_ACCOUNT, EXTRACTOR_ACCOUNT, id);
}
Run Code Online (Sandbox Code Playgroud)

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

更新2:与结合可选的用于可选的值,而不是空.

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

现在使用时可以使用以下内容:

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);
}
Run Code Online (Sandbox Code Playgroud)


Phi*_*all 21

这不是一个好的解决方案,因为你依赖于控制流的异常.在您的解决方案中,获取异常是正常的,将它们放在日志中是正常的.

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'";
    List<String> certs = jdbc.queryForList(sql, String.class); 
    if (certs.isEmpty()) {
        return null;
    } else {
        return certs.get(0);
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 7

实际上,您可以JdbcTemplate根据需要使用和自定义自己的方法.我的建议是做这样的事情:

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'";
    ArrayList<String> certList = (ArrayList<String>) jdbc.query(
        sql, new RowMapperResultSetExtractor(new UserMapper()));
    cert =  DataAccessUtils.singleResult(certList);

    return cert;
}
Run Code Online (Sandbox Code Playgroud)

它起原作用jdbc.queryForObject,但没有throw new EmptyResultDataAccessException时间size == 0.

  • `DataAccessUtils.singleResult(...)` 就是我正在寻找的。谢谢 (2认同)

Ste*_*ans 7

由于在没有数据时返回null是我在使用queryForObject时经常要做的事情,我发现扩展JdbcTemplate并添加类似于下面的queryForNullableObject方法很有用.

public class JdbcTemplateExtended extends JdbcTemplate {

    public JdbcTemplateExtended(DataSource datasource){
        super(datasource);
    }

    public <T> T queryForNullableObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
        List<T> results = query(sql, rowMapper);

        if (results == null || results.isEmpty()) {
            return null;
        }
        else if (results.size() > 1) {
            throw new IncorrectResultSizeDataAccessException(1, results.size());
        }
        else{
            return results.iterator().next();
        }
    }

    public <T> T queryForNullableObject(String sql, Class<T> requiredType) throws DataAccessException {
        return queryForObject(sql, getSingleColumnRowMapper(requiredType));
    }

}
Run Code Online (Sandbox Code Playgroud)

您现在可以像使用queryForObject一样在代码中使用它

String result = queryForNullableObject(queryString, String.class);
Run Code Online (Sandbox Code Playgroud)

我很想知道是否有其他人认为这是个好主意?


Byr*_*ron 6

好的,我明白了.我只是将它包装在try catch中并发送回null.

    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'";
            try {
                Object o = (String) jdbc.queryForObject(sql, String.class);
                cert = (String) o;
            } catch (EmptyResultDataAccessException e) {
                e.printStackTrace();
            }
            return cert;
    }
Run Code Online (Sandbox Code Playgroud)

  • 我不明白为什么这会如此糟糕,为什么你收到了如此多的反对票,因为它是“例外中没有程序流”原则的原教旨主义者。我只想用解释案例的注释替换打印堆栈跟踪,而不做其他任何事情。 (4认同)

Sam*_*ipp 6

使用 Java 8 或更高版本,您可以使用Optional和 Java Streams。

因此,您可以简单地使用该JdbcTemplate.queryForList()方法,创建一个 Stream 并使用Stream.findFirst()它将返回 Stream 的第一个值或一个空值Optional

public Optional<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.queryForList(sql, String.class)
            .stream().findFirst();
}
Run Code Online (Sandbox Code Playgroud)

为了提高查询的性能,您可以附加LIMIT 1到您的查询,因此从数据库传输的项目不超过 1 个。


Ant*_*iev 5

你可以这样做:

String cert = DataAccessUtils.singleResult(
    jdbcTemplate.queryForList(
        "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN where ID_STR_RT = :id_str_rt and ID_NMB_SRZ = :id_nmb_srz",
        new MapSqlParameterSource()
            .addValue("id_str_rt", "999")
            .addValue("id_nmb_srz", "60230009999999"),
        String.class
    )
)
Run Code Online (Sandbox Code Playgroud)

DataAccessUtils.singleResult + queryForList

  • 对于空结果返回 null
  • 如果恰好找到 1 行,则返回单个结果
  • 如果找到多于 1 行,则抛出异常。主键/唯一索引搜索不应发生

DataAccessUtils.singleResult