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