Запрос 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

Мне казалось, что я должен просто вернуть ноль вместо того, чтобы генерировать исключение. Как я могу это исправить? Заранее спасибо.


person Byron    schedule 15.05.2012    source источник


Ответы (18)


В 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.    
}
person Rakesh Juyal    schedule 16.05.2012
comment
Как упоминалось ниже, единственным недостатком здесь является то, что если бы возвращаемый тип был сложным типом, вы бы строили несколько объектов и создавали список, а также ResultSet.next() вызывался бы без необходимости. В этом случае использование ResultSetExtractor является гораздо более эффективным инструментом. - person Brett Ryan; 06.05.2013
comment
В определении анонимного класса отсутствуют скобки — новый RowMapper () - person Janis Koluzs; 09.12.2013
comment
Привет, @Rakesh, почему не только return null в catch(EmptyResultDataAccessException exception){ return null; }? - person Vishal Zanzrukia; 19.06.2014
comment
@Rakesh Это не то же самое для всех методов, таких как queryForXXX. В случае queryForList возвращает пустой список. - person Sankalp; 26.02.2015
comment
Привет! Могу я просто спросить, почему «Теперь правильный способ - не перехватывать это исключение», учитывая, что вы используете queryForObject? Что плохого в перехвате исключения в случае с queryForObject? Спасибо :) - person Michael Stokes; 12.12.2016
comment
@Rakesh Juyal Пожалуйста, измените new RowMapper на new RowMapper<String>(), так как я чуть не убил себя, пытаясь понять, почему это не компилируется :) Также я думаю, что изменение типа возврата на String было бы полезно. - person Dmitry Senkovich; 10.06.2017
comment
но убедитесь, что используемый вами запрос должен возвращать только одну строку. Это безумие. Это похоже на диктовку или принуждение пользователя, вместо этого это должно было быть обработано во фреймворке. Я вижу это как пробел в фреймворке или он плохо обрабатывается фреймворком. Вы не можете решить, как должен выглядеть мой запрос. Если я даю запрос фреймворку, он должен обрабатывать основные вещи. Ведь для этого он и предназначен.. - person Stunner; 04.11.2020

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

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

ОБНОВЛЕНИЕ. Прошло несколько лет, и я хочу поделиться несколькими хитростями. JdbcTemplate отлично работает с лямбда-выражениями java 8, для которых предназначены следующие примеры, но вы можете довольно легко использовать статический класс для достижения того же.

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

Прежде всего. Предположим, что у вас есть объект учетной записи с двумя свойствами для простоты 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 =
        singletonExtractor(MAPPER_ACCOUNT);

public Account getAccount(long id) {
    return jt.query(SELECT_ACCOUNT, EXTRACTOR_ACCOUNT, id);
}

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

ОБНОВЛЕНИЕ 2: объединить с Необязательный для необязательных значений вместо нулевого.

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);
}
person Brett Ryan    schedule 06.05.2013

Это не очень хорошее решение, потому что вы полагаетесь на исключения для потока управления. В вашем решении нормально получать исключения, нормально иметь их в журнале.

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);
    }
}
person Philippe Marschall    schedule 15.05.2012
comment
мое решение может быть не самым элегантным, но, по крайней мере, мое работает. Вы приводите пример queryForObjectList, который даже не является опцией с Jdbctemplate. - person Byron; 16.05.2012
comment
Единственным недостатком здесь является то, что если бы возвращаемый тип был сложным типом, вы бы строили несколько объектов и создавали бы список, а также ResultSet.next() вызывался бы без необходимости. В этом случае использование ResultSetExtractor является гораздо более эффективным инструментом. - person Brett Ryan; 06.05.2013
comment
а что, если не иметь значения - это вариант, но не иметь более одного? У меня часто бывает этот шаблон, и для этой цели я хотел бы иметь queryForOptionalObject в Spring. - person Guillaume; 06.02.2015
comment
Комментарий @Guillaume к этому PR github.com/spring-projects/spring-framework/ тянуть/724 - person Philippe Marschall; 01.07.2021

Хорошо, я понял это. Я просто завернул его в попытку поймать и отправить обратно 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;
    }
person Byron    schedule 15.05.2012
comment
Я не понимаю, почему это так плохо и почему вы получили за это так много отрицательных голосов, кроме того, что вы являетесь фундаменталистом по принципу «нет потока программы в пределах исключения». Я бы просто заменил трассировку стека на комментарий, объясняющий случай, и больше ничего не делал. - person Guillaume; 06.02.2015
comment
Этот комментарий в 2020 году, и ваш ответ все еще в силе. Такой огромный пробел в рамках.. - person Stunner; 04.11.2020

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

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

person Stewart Evans    schedule 25.02.2014
comment
Так и должно быть весной - person Guillaume; 06.02.2015

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

person Alex    schedule 12.07.2013
comment
@Абдулл UserMapper implements RowMapper<String>. - person Brett Ryan; 27.05.2016
comment
DataAccessUtils.singleResult(...) это то, что я искал. Спасибо - person Drakes; 15.01.2020

Используя Java 8 или выше, вы можете использовать Optional и потоки Java.

Таким образом, вы можете просто использовать метод JdbcTemplate.queryForList(), создать поток и использовать Stream.findFirst(), который вернет первое значение потока или пустой 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 элемента.

person Samuel Philipp    schedule 26.08.2019

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

MIN(ID_NMB_SRZ)
person DS.    schedule 18.06.2012

Поскольку getJdbcTemplate().queryForMap ожидает минимальный размер, равный единице, но когда он возвращает значение null, он показывает исправление EmptyResultDataAccesso, когда можно использовать приведенную ниже логику.

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;
}
person Mahesh Jayachandran    schedule 21.05.2014

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

SELECT (SELECT <query>) AS value

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

person Rich    schedule 05.07.2014

Мы можем использовать query вместо queryForObject, основное различие между query и queryForObject заключается в том, что запрос возвращает список Object (на основе типа возвращаемого значения Row mapper), и этот список может быть пустым, если данные не получены из базы данных, в то время как 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);
      }
  }
person ABHAY JOHRI    schedule 07.11.2017

Вы можете сделать это следующим образом:

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

person Anton Yuriev    schedule 22.09.2020

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

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

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

person grbonk    schedule 25.03.2014

Для Байрона можно попробовать это..

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();
        }
person Mohan Kumar Dg    schedule 07.08.2015

делать

    jdbcTemplate.queryForList(sql, String.class)

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

    org.springframework.jdbc.core.JdbcTemplate
person Dmitry    schedule 19.08.2015

IMHO, возвращающий null, является плохим решением, потому что теперь у вас есть проблема с отправкой и интерпретацией его на (вероятном) внешнем клиенте. У меня была такая же ошибка, и я решил ее, просто вернув List<FooObject>. Я использовал JDBCTemplate.query().

Во внешнем интерфейсе (веб-клиент Angular) я просто просматриваю список и, если он пуст (нулевой длины), считаю, что записи не найдены.

person likejudo    schedule 21.01.2020

У меня такое же исключение при использовании

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

при изменении списка он разрешился

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

Я просто ловлю это "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.
}
person Eddy    schedule 16.11.2016
comment
Мы можем использовать запрос вместо queryForObject - person ABHAY JOHRI; 18.12.2017
comment
Обычно считается плохой практикой злоупотреблять исключениями, подобными этому. Исключения предназначены не для предсказуемой логики программы, а для исключительных ситуаций. - person Chris Baker; 26.07.2018