【问题标题】:Jdbctemplate query for string: EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0字符串的 Jdbctemplate 查询:EmptyResultDataAccessException:不正确的结果大小:预期为 1,实际为 0
【发布时间】:2012-05-23 06:56:16
【问题描述】:

我正在使用 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'";
        cert = (String) jdbc.queryForObject(sql, String.class); 
        return cert;
    }

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

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

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

【问题讨论】:

    标签: java spring jdbctemplate


    【解决方案1】:

    在 JdbcTemplate 、queryForIntqueryForLongqueryForObject 中,所有此类方法都期望执行的查询将返回一行且仅返回一行。如果您没有得到任何行或多于一行,则会导致 IncorrectResultSizeDataAccessException 。现在正确的方法不是捕获这个异常或EmptyResultDataAccessException,而是确保您使用的查询应该只返回一行。如果根本不可能,请改用query 方法。

    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
    }
    

    【讨论】:

    • 如下所述,这里唯一的缺点是,如果返回类型是复杂类型,您将构建多个对象并实例化一个列表,ResultSet.next() 也会被不必要地调用。在这种情况下,使用ResultSetExtractor 是一种更有效的工具。
    • 嗨@Rakesh,为什么不把return null 放在catch(EmptyResultDataAccessException exception){ return null; } 里?
    • @Rakesh 这对于 queryForXXX 等所有方法并不相同。在 queryForList 的情况下,它返回一个空列表。
    • 嘿!考虑到您是否使用 queryForObject,我可以问为什么“现在正确的方法不是捕获此异常”吗?在 queryForObject 的情况下捕获异常会有什么问题?谢谢:)
    • “但请确保您使用的查询应该只返回一行”。这太疯狂了。这就像口授或强迫用户,而不是应该在框架中处理。我认为这是框架中的一个差距,或者它没有在框架中得到很好的处理。你无法决定我的查询应该是什么样子。如果我向 framework 查询,它应该处理基本的东西。毕竟这就是它的意义..
    【解决方案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 完美配合,以下示例是为这些 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);
    }
    

    【讨论】:

      【解决方案3】:

      这不是一个好的解决方案,因为您依赖异常来控制流。在您的解决方案中,出现异常是正常的,将它们记录在日志中是正常的。

      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 的一个选项。
      • 这里唯一的缺点是,如果返回类型是复杂类型,您将构建多个对象并实例化一个列表,ResultSet.next() 也会被不必要地调用。在这种情况下,使用ResultSetExtractor 是一种更有效的工具。
      • 如果没有价值是一种选择,但没有超过一个怎么办?我经常有这种模式,为此我想在 Spring 中有一个 queryForOptionalObject。
      • @Guillaume 对此 PR 发表评论 github.com/spring-projects/spring-framework/pull/724
      【解决方案4】:

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

      【讨论】:

      • 我不明白为什么这会如此糟糕,为什么您会因此而受到如此多的反对,因为它是“异常内没有程序流”原则的原教旨主义者。我只会用解释案例的注释替换 printstack 跟踪,而不做任何其他事情。
      • 这个评论是2020年的,你的回答还是有效的。。这么大的框架差距。。
      【解决方案5】:

      由于在没有数据时返回 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);
      

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

      【讨论】:

      • 是的,而且应该在春天
      【解决方案6】:

      实际上,您可以使用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 一样工作,但在size == 0 时没有throw new EmptyResultDataAccessException

      【讨论】:

      • @Abdull UserMapper implements RowMapper&lt;String&gt;.
      • DataAccessUtils.singleResult(...) 是我想要的。谢谢
      【解决方案7】:

      使用 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();
      }
      

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

      【讨论】:

        【解决方案8】:

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

        MIN(ID_NMB_SRZ)
        

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

            你可以这样做:

            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

            【讨论】:

              【解决方案11】:

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

              SELECT (SELECT <query>) AS value
              

              从而避免调用者的复杂性。

              【讨论】:

                【解决方案12】:

                我们可以使用 query 代替 queryForObject,query 和 queryForObject 的主要区别在于查询返回的 Object 列表(基于 Row mapper 返回类型),如果没有从数据库接收到数据,则该列表可以为空,而 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);
                      }
                  }
                

                【讨论】:

                  【解决方案13】:

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

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

                  性能不清楚,但它可以工作并且很整洁。

                  【讨论】:

                    【解决方案14】:

                    拜伦,你可以试试这个..

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

                    【讨论】:

                      【解决方案15】:

                      制作

                          jdbcTemplate.queryForList(sql, String.class)
                      

                      工作,确保你的 jdbcTemplate 是类型

                          org.springframework.jdbc.core.JdbcTemplate
                      

                      【讨论】:

                        【解决方案16】:

                        恕我直言,返回 null 是一个糟糕的解决方案,因为现在您遇到了在(可能的)前端客户端发送和解释它的问题。 我遇到了同样的错误,我通过简单地返回 List&lt;FooObject&gt; 来解决它。 我用JDBCTemplate.query()

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

                        【讨论】:

                          【解决方案17】:

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

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

                          当更改为列表时已解决

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

                          【讨论】:

                            【解决方案18】:

                            我刚刚发现了这个“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
                            • 通常被认为是滥用此类异常的不良做法。异常不是针对可预测的程序逻辑流,而是针对异常情况。
                            猜你喜欢
                            • 2017-06-02
                            • 2015-03-13
                            • 2015-04-24
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2016-01-05
                            • 2023-04-04
                            相关资源
                            最近更新 更多