Jdbctemplate查询字符串:EmptyResultDataAccessException:错误的结果大小:预期为1,实际为0


105

我正在使用Jdbctemplate从数据库中检索单个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;
    }

在我的情况下,完全可以在查询中不击中,所以我的问题是如何解决以下错误消息。

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

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

Answers:


179

在JdbcTemplate,,queryForIntqueryForLongqueryForObject所有这些方法都希望执行的查询将返回一行且仅返回一行。如果没有行或多于一行,将导致IncorrectResultSizeDataAccessException。现在正确的方法不是捕获此异常或EmptyResultDataAccessException,而是确保您使用的查询仅返回一行。如果根本不可能,则使用querymethod代替。

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.    
}

如下所述,这里的唯一缺点是,如果返回类型是复杂类型,则您将构建多个对象并实例化一个列表,这也ResultSet.next()将不必要地被调用。ResultSetExtractor在这种情况下,使用a 是更有效的工具。
布雷特·瑞安

3
匿名类定义中缺少括号
-new

我和布雷特在一起。ResultSetExtractor更干净:)
laher 2014年

2
嗨@Rakesh,为什么不只return nullcatch(EmptyResultDataAccessException exception){ return null; }
Vishal Zanzrukia 2014年

1
嘿! 考虑到您是否正在使用queryForObject,我是否可以问为什么“现在的正确方法是不捕获此异常”?在queryForObject的情况下捕获异常怎么办?谢谢:)
迈克尔·斯托克斯

48

您也可以使用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 lambda完美配合使用,但是您可以轻松地使用静态类来实现相同功能。

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

首先。为了简单起见,我们假设您有一个带有两个属性的account对象Account(Long id, String name)。您可能希望RowMapper为此域对象添加一个。

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

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

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:与Optional组合以获得可选值,而不是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);
}

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);
    }
}

我的解决方案可能不是最优雅的,但至少是我的解决方案。您给出一个queryForObjectList的示例,它甚至不是Jdbctemplate的选项。
拜伦

1
唯一的缺点是,如果返回类型是复杂类型,则将构建多个对象并实例化一个列表,这也ResultSet.next()将不必要地被调用。ResultSetExtractor在这种情况下,使用a 是更有效的工具。
布雷特·瑞安

如果没有价值是一个选择,但不超过一个,该怎么办?我经常有这种模式,为此我想在Spring中使用queryForOptionalObject。
Guillaume

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;
}

它像原始版本一样工作jdbc.queryForObject,但是没有throw new EmptyResultDataAccessException时间size == 0


@Abdull UserMapper implements RowMapper<String>
布雷特·瑞安

我认为这是最好的答案,因为它给出了最短的语法
Stan Sokolov,2016年

DataAccessUtils.singleResult(...)是我一直在寻找的东西。Thx
德雷克斯

7

因为在没有数据时返回空值是我在使用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));
    }

}

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

String result = queryForNullableObject(queryString, String.class);

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


1
是的,应该在春天
Guillaume

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;
    }

1
我不明白为什么这很糟糕,为什么您会因此而遭受如此之低的批评,这是因为作为“没有例外之内的程序流”原则的原教旨主义者。我只需要用解释情况的注释替换printstack跟踪,就什么也不做。
Guillaume

4

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

因此,您可以简单地使用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();
}

为了提高查询的性能,您可以将其追加LIMIT 1到查询中,以便从数据库中传输不超过1个项目。


1
干净整洁。没有其他ifs或lambdas。我喜欢。
BeshEater

2

您可以使用组函数,以便您的查询始终返回结果。即

MIN(ID_NMB_SRZ)

1

在Postgres中,您可以通过包装将几乎所有单个值查询返回一个值或null:

SELECT (SELECT <query>) AS value

因此避免了呼叫者的复杂性。


1

由于getJdbcTemplate()。queryForMap期望的最小大小为1,但是当它返回null时,它显示EmptyResultDataAccesso修复dis何时可以使用以下逻辑

Map<String, String> loginMap =null;
try{
    loginMap = getJdbcTemplate().queryForMap(sql, new Object[] {CustomerLogInInfo.getCustLogInEmail()});
}
catch(EmptyResultDataAccessException ex){
    System.out.println("Exception.......");
    loginMap =null;
}
if(loginMap==null || loginMap.isEmpty()){
    return null;
}
else{
    return loginMap;
}

0

我之前处理过这个问题,并已在春季论坛上发帖。

http://forum.spring.io/forum/spring-projects/data/123129-frustrated-with-emptyresultdataaccessexception

我们收到的建议是使用一种SQlQuery。这是我们尝试从可能不存在的数据库中获取值时所做的示例。

@Component
public class FindID extends MappingSqlQuery<Long> {

        @Autowired
        public void setDataSource(DataSource dataSource) {

                String sql = "Select id from address where id = ?";

                super.setDataSource(dataSource);

                super.declareParameter(new SqlParameter(Types.VARCHAR));

                super.setSql(sql);

                compile();
        }

        @Override
        protected Long mapRow(ResultSet rs, int rowNum) throws SQLException {
                return rs.getLong(1);
        }

然后在DAO中,我们只需调用...

Long id = findID.findObject(id);

在性能上尚不清楚,但它可以正常工作。


0

对于拜伦,您可以尝试一下。

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> li = jdbcTemplate.queryForList(sql,String.class);
                return li.get(0).toString();
        }

0

使

    jdbcTemplate.queryForList(sql, String.class)

工作,请确保您的jdbcTemplate是类型

    org.springframework.jdbc.core.JdbcTemplate

0

我们可以使用query而不是queryForObject,query和queryForObject之间的主要区别是对象的查询返回列表(基于行映射器返回类型),并且如果没有从数据库接收到数据,则该列表可以为空,而queryForObject总是期望只有一个对象是从db既不获取null也不获取多行,并且如果结果为空,则queryForObject抛出EmptyResultDataAccessException,我已经使用查询编写了一个代码,该查询将克服在结果为null的情况下EmptyResultDataAccessException的问题。

----------


public UserInfo getUserInfo(String username, String password) {
      String sql = "SELECT firstname, lastname,address,city FROM users WHERE id=? and pass=?";
      List<UserInfo> userInfoList = jdbcTemplate.query(sql, new Object[] { username, password },
              new RowMapper<UserInfo>() {
                  public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
                      UserInfo user = new UserInfo();
                      user.setFirstName(rs.getString("firstname"));
                      user.setLastName(rs.getString("lastname"));
                      user.setAddress(rs.getString("address"));
                      user.setCity(rs.getString("city"));

                      return user;
                  }
              });

      if (userInfoList.isEmpty()) {
          return null;
      } else {
          return userInfoList.get(0);
      }
  }

0

恕我直言,返回a null是一个不好的解决方案,因为现在您遇到了在(可能)前端客户端发送和解释它的问题。我也遇到了同样的错误,只需返回即可解决List<FooObject>。我用过JDBCTemplate.query()

在前端(Angular Web客户端),我仅检查列表,如果列表为空(长度为零),则将其视为未找到记录。


-1

我只是抓住了这个“ EmptyResultDataAccessException”

public Myclass findOne(String id){
    try {
        Myclass m = this.jdbcTemplate.queryForObject(
                "SELECT * FROM tb_t WHERE id = ?",
                new Object[]{id},
                new RowMapper<Myclass>() {
                    public Myclass mapRow(ResultSet rs, int rowNum) throws SQLException {
                        Myclass m = new Myclass();
                        m.setName(rs.getString("name"));
                        return m;
                    }
                });
        return m;
    } catch (EmptyResultDataAccessException e) { // result.size() == 0;
        return null;
    }
}

那么您可以检查:

if(m == null){
    // insert operation.
}else{
    // update operation.
}

我们可以使用查询代替queryForObject
ABHAY JOHRI

1
通常认为滥用此类异常行为是不好的做法。异常不是可预测的程序逻辑流,而是异常情况。
克里斯·贝克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.