Запрос Jdbctemplate для строки: EmptyResultDataAccessException: неверный размер результата: ожидаемый 1, фактический 0


Я использую 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 вместо того, чтобы выбрасывать исключение. Как я могу это исправить? Спасибо заранее.

14 80

14 ответов:

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

вы также можете использовать 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;
        }
    });
}

The ResultSetExtractor имеет дополнительное преимущество, что вы можете обрабатывать все случаи, где есть более одной строки или нет возвращаемых строк.

обновление: несколько лет и у меня есть несколько трюков, чтобы поделиться. JdbcTemplate строительство великолепно с java 8 lambdas, для которых предназначены следующие примеры, но вы можете довольно легко использовать статический класс для достижения того же самого.

хотя речь идет о простых типах, эти примеры служат руководством для общего случая извлечения объектов домена.

во-первых. Предположим, что у вас есть объект 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 объекты домена из запроса (jt это JdbcTemplate экземпляр).

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 =
        nullableExtractor(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 =
        nullableOptionalExtractor(MAPPER_DISCOUNT);

public double getDiscount(long accountId) {
    return jt.query(SELECT_DISCOUNT, EXTRACTOR_DISCOUNT, accountId)
            .orElse(0.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> certs = jdbc.queryForList(sql, String.class); 
    if (certs.isEmpty()) {
        return null;
    } else {
        return certs.get(0);
    }
}

на самом деле, вы можете играть с 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.

поскольку возврат 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);

мне было бы интересно узнать, если кто-то еще думает, что это хорошая идея?

хорошо, я понял это. Я просто завернул его в 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;
    }

вы можете использовать функцию группы, чтобы ваш запрос всегда возвращал результат. т. е.

MIN(ID_NMB_SRZ)

в Postgres вы можете сделать почти любой запрос с одним значением возвращающим значение или null, обернув его:

SELECT (SELECT <query>) AS value

и, следовательно, избежать сложности в вызвавшей.

начиная с getJdbcTemplate ().queryForMap ожидает минимальный размер одного, но когда он возвращает null, он показывает EmptyResultDataAccesso fix dis, когда можно использовать ниже logic

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

Я занимался этим раньше и опубликовал на весенних форумах.

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

в ДАО тогда мы просто называем...

Long id = findID.findObject(id);

не ясно по производительности, но это работает и аккуратно.

Байрона, вы можете попробовать это..

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

сделать

    jdbcTemplate.queryForList(sql, String.class)

работа, убедитесь, что ваш jdbcTemplate имеет тип

    org.springframework.jdbc.core.JdbcTemplate

мы можем использовать запрос вместо queryForObject, основное различие между запросом и queryForObject заключается в том, что запрос возвращает список объектов(на основе сопоставления строк возвращаемого типа) и что список может быть пустым, если нет данных, полученных из базы данных, а queryForObject всегда ожидала лишь одного объекта быть извлечена из БД ни нуль, ни на несколько строк и в случае если результат пустой, то queryForObject бросает EmptyResultDataAccessException, я написал код, используя запрос, который позволит преодолеть проблему 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);
      }
  }

Я просто ловлю это "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.
}