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

Ответы:


179

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

Как упоминалось ниже, единственным недостатком здесь является то, что если бы возвращаемый тип был сложным типом, вы бы создавали несколько объектов и создавали бы список, что также ResultSet.next()было бы вызвано без необходимости. В этом случае использование a ResultSetExtractor- гораздо более эффективный инструмент.
Бретт Райан

3
В определении анонимного класса отсутствуют круглые скобки - new RowMapper ()
Янис Колуз

Я с Бреттом в этом. ResultSetExtractor чище :)
laher 06

2
Привет @ Ракеш, а почему не только return nullв catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia

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, для которых предназначены следующие примеры, но вы можете легко использовать статический класс для достижения того же.

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

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

Создание ResultSetExtractornow становится тривиальным.

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

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()было бы вызвано без необходимости. В этом случае использование a ResultSetExtractor- гораздо более эффективный инструмент.
Бретт Райан

а что, если вариант не имеет ценности, но не более одной? У меня часто используется этот шаблон, и я хотел бы иметь для этой цели queryForOptionalObject в Spring.
Гийом

7

Хорошо, я разобрался. Я просто обернул его в 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 комментарием, объясняющим случай, и больше ничего не делал.
Гийом

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.


@ Абдулл UserMapper implements RowMapper<String>.
Бретт Райан

Я думаю, что это лучший ответ здесь, поскольку он дает кратчайший синтаксис
Стэн Соколов

DataAccessUtils.singleResult(...)это то, что я искал. Thx
Drakes

7

Поскольку при использовании queryForObject я часто хочу возвращать значение NULL при отсутствии данных, я счел полезным расширить 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
Это и должно быть весной
Гийом

4

Используя 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 элемента.


1
Красиво и чисто. Никаких дополнительных if или лямбдов. Мне это нравится.
BeshEater

2

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

MIN(ID_NMB_SRZ)

1

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

SELECT (SELECT <query>) AS value

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


1

Поскольку getJdbcTemplate (). QueryForMap ожидает минимальный размер, равный единице, но когда он возвращает 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;
}

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

0

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

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


-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
АБХАЙ ДЖОХРИ

1
Обычно считается плохой практикой злоупотреблять такими исключениями. Исключения предназначены не для предсказуемой логики программы, а для исключительных ситуаций.
Крис Бейкер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.