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

Я использую Jdbctemplate для извлечения единственного значения String из db. Вот мой метод.

    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

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

Ответ 1

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

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

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

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

Ответ 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);
    }
}

Ответ 4

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

Ответ 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

Хорошо, я понял это. Я просто завернул его в попытку поймать и отправить обратно 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;
    }

Ответ 7

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

MIN(ID_NMB_SRZ)

Ответ 8

Так как 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;
}

Ответ 9

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

SELECT (SELECT <query>) AS value

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

Ответ 10

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

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

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

Ответ 11

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

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

Ответ 12

сделать

    jdbcTemplate.queryForList(sql, String.class)

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

    org.springframework.jdbc.core.JdbcTemplate

Ответ 13

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

Ответ 14

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

Таким образом, вы можете просто использовать метод 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 элемента.

Ответ 15

Я просто поймаю это "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.
}