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

时间:2012-05-15 17:55:58

标签: 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;
    }

在我的场景中,完全可能不会对我的查询产生影响所以我的问题是如何解决以下错误消息。

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

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

18 个答案:

答案 0 :(得分:157)

在JdbcTemplate中,queryForIntqueryForLongqueryForObject所有此类方法都希望执行的查询将返回一行而且只返回一行。如果没有行或多行将导致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.    
}

答案 1 :(得分:37)

您也可以使用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,以下示例是为其设计的,但您可以非常轻松地使用静态类来实现相同的目标。

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

首先关闭。假设您有一个帐户对象,其中包含两个属性,以简化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);
}

答案 2 :(得分:19)

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

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

答案 3 :(得分: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

答案 4 :(得分: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));
    }

}

现在,您可以像使用queryForObject

一样在代码中使用它
String result = queryForNullableObject(queryString, String.class);

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

答案 5 :(得分:5)

好的,我明白了。我只是将它包装在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;
    }

答案 6 :(得分:2)

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

MIN(ID_NMB_SRZ)

答案 7 :(得分:2)

使用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个项目。

答案 8 :(得分:1)

在Postgres中,您可以通过包装它来使几乎任何单个值查询返回值或null:

SELECT (SELECT <query>) AS value

因此避免了调用者的复杂性。

答案 9 :(得分:1)

因为getJdbcTemplate()。queryForMap要求最小大小为1,但是当它返回null时,它会显示EmptyResultDataAccesso fix 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;
}

答案 10 :(得分: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();
        }

答案 11 :(得分:0)

制作

    jdbcTemplate.queryForList(sql, String.class)

工作,确保您的jdbcTemplate类型为

    org.springframework.jdbc.core.JdbcTemplate

答案 12 :(得分:0)

我们可以使用query而不是queryForObject,查询和queryForObject之间的主要区别是Object的查询返回列表(基于Row mapper返回类型),如果没有从数据库接收数据,该列表可以为空,而queryForObject总是只期望从db获取单个对象既不是null也不是多行,如果result为空,则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);
      }
  }

答案 13 :(得分:0)

我之前和之前处理过这个问题。曾在春季论坛发帖。

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

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

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

不清楚性能,但它有效并且很整洁。

答案 14 :(得分:0)

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

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

答案 15 :(得分:0)

您可以这样:

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

DataAccessUtils.singleResult + queryForList

  • 为空结果返回null
  • 如果仅找到1行,则返回单个结果
  • 如果发现多于1行,则
  • 引发异常。主键/唯一索引搜索不应该发生

DataAccessUtils.singleResult

答案 16 :(得分:0)

我在使用时遇到了同样的异常

if(jdbcTemplate.queryForMap("select * from table").size() > 0 ) /* resulted exception */ 

当更改为列表时它已解决

if(jdbcTemplate.queryForList("select * from table").size() > 0) /* no exception */

答案 17 :(得分:-1)

我抓住了这个&#34; EmptyResultDataAccessException&#34;

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