Consulta de Jdbctemplate para cadena: EmptyResultDataAccessException: Tamaño de resultado incorrecto: esperado 1, actual 0

Estoy usando Jdbctemplate para recuperar un solo valor de cadena de db. Aquí está mi método.

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

En mi caso, es posible NO obtener un golpe en mi consulta, así que mi pregunta es ¿cómo puedo evitar el siguiente mensaje de error?

 EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0 

Me parece que debería obtener un nulo en lugar de lanzar una excepción. ¿Cómo puedo arreglar esto? Gracias por adelantado.

En JdbcTemplate, queryForInt , queryForLong , queryForObject todos estos métodos esperan que la consulta ejecutada devuelva una y solo una fila. Si no obtiene filas o más de una fila, dará como resultado IncorrectResultSizeDataAccessException . Ahora la forma correcta es no detectar esta excepción o EmptyResultDataAccessException , pero asegúrese de que la consulta que está utilizando debe devolver solo una fila. Si no es posible, entonces use el método de query lugar.

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

También puede usar un ResultSetExtractor lugar de un RowMapper . Ambos son tan fáciles como los demás, la única diferencia es que se llama a 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() { @Override public String extractData(ResultSet rs) throws SQLException, DataAccessException { return rs.next() ? rs.getString("ID_NMB_SRZ") : null; } }); } 

El ResultSetExtractor tiene el beneficio adicional de que puede manejar todos los casos en los que hay más de una fila o no se devuelven las filas.

ACTUALIZACIÓN : Varios años después y tengo algunos trucos para compartir. JdbcTemplate funciona magníficamente con java 8 lambdas para los cuales están diseñados los siguientes ejemplos, pero puedes usar fácilmente una clase estática para lograr lo mismo.

Si bien la pregunta es acerca de los tipos simples, estos ejemplos sirven como una guía para el caso común de extracción de objetos de dominio.

Antes que nada. Supongamos que tiene un objeto de cuenta con dos propiedades para simplificar la Account(Long id, String name) . Es probable que desee tener un RowMapper para este objeto de dominio.

 private static final RowMapper MAPPER_ACCOUNT = (rs, i) -> new Account(rs.getLong("ID"), rs.getString("NAME")); 

Ahora puede usar este asignador directamente dentro de un método para mapear objetos de dominio de Account de una consulta ( jt es una instancia de JdbcTemplate ).

 public List getAccounts() { return jt.query(SELECT_ACCOUNT, MAPPER_ACCOUNT); } 

Genial, pero ahora queremos nuestro problema original y utilizamos mi solución original reutilizando RowMapper para realizar el mapeo por nosotros.

 public Account getAccount(long id) { return jt.query( SELECT_ACCOUNT, rs -> rs.next() ? MAPPER_ACCOUNT.mapRow(rs, 1) : null, id); } 

Genial, pero este es un patrón que puedes y querrás repetir. Entonces puede crear un método de fábrica genérico para crear un nuevo ResultSetExtractor para la tarea.

 public static  ResultSetExtractor singletonExtractor( RowMapper< ? extends T> mapper) { return rs -> rs.next() ? mapper.mapRow(rs, 1) : null; } 

Crear un ResultSetExtractor ahora se vuelve trivial.

 private static final ResultSetExtractor EXTRACTOR_ACCOUNT = nullableExtractor(MAPPER_ACCOUNT); public Account getAccount(long id) { return jt.query(SELECT_ACCOUNT, EXTRACTOR_ACCOUNT, id); } 

Espero que esto ayude a mostrar que ahora puedes combinar fácilmente partes de una manera poderosa para simplificar tu dominio.

ACTUALIZACIÓN 2 : Combine con un Opcional para valores opcionales en lugar de nulo.

 public static  ResultSetExtractor> singletonOptionalExtractor( RowMapper< ? extends T> mapper) { return rs -> rs.next() ? Optional.of(mapper.mapRow(rs, 1)) : Optional.empty(); } 

Que ahora cuando se usa podría tener lo siguiente:

 private static final ResultSetExtractor> EXTRACTOR_DISCOUNT = nullableOptionalExtractor(MAPPER_DISCOUNT); public double getDiscount(long accountId) { return jt.query(SELECT_DISCOUNT, EXTRACTOR_DISCOUNT, accountId) .orElse(0.0); } 

Esa no es una buena solución porque confía en excepciones para el flujo de control. En su solución, es normal obtener excepciones, es normal tenerlas en el registro.

 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 certs = jdbc.queryForList(sql, String.class); if (certs.isEmpty()) { return null; } else { return certs.get(0); } } 

En realidad, puedes jugar con JdbcTemplate y personalizar tu propio método como prefieras. Mi sugerencia es hacer algo como esto:

 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 certList = (ArrayList) jdbc.query( sql, new RowMapperResultSetExtractor(new UserMapper())); cert = DataAccessUtils.singleResult(certList); return cert; } 

Funciona como el jdbc.queryForObject original, pero sin throw new EmptyResultDataAccessException cuando size == 0 .

Como devolver un valor nulo cuando no hay datos es algo que quiero hacer a menudo cuando uso queryForObject, he encontrado que es útil extender JdbcTemplate y agregar un método queryForNullableObject similar al siguiente.

 public class JdbcTemplateExtended extends JdbcTemplate { public JdbcTemplateExtended(DataSource datasource){ super(datasource); } public  T queryForNullableObject(String sql, RowMapper rowMapper) throws DataAccessException { List 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 queryForNullableObject(String sql, Class requiredType) throws DataAccessException { return queryForObject(sql, getSingleColumnRowMapper(requiredType)); } } 

Ahora puede usar esto en su código de la misma manera que utilizó queryForObject

 String result = queryForNullableObject(queryString, String.class); 

Me interesaría saber si alguien más piensa que esta es una buena idea.

Ok, lo descubrí. Acabo de envolverlo en una captura de prueba y devolver nulo.

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

Puede usar una función de grupo para que su consulta siempre devuelva un resultado. es decir

 MIN(ID_NMB_SRZ) 

En Postgres, puede hacer que casi cualquier consulta de valor único devuelva un valor o nulo envolviéndolo:

 SELECT (SELECT ) AS value 

y por lo tanto, evitar la complejidad en la persona que llama.

Como getJdbcTemplate (). QueryForMap espera un tamaño mínimo de uno, pero cuando devuelve nulo, muestra EmptyResultDataAccesso fix dis cuando puede usar la lógica de abajo

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

He tratado esto antes y lo había publicado en los foros de spring.

http://forum.spring.io/forum/spring-projects/data/123129-frustrated-with-emptyresultdataaccessexception

El consejo que recibimos fue usar un tipo de SQlQuery. Aquí hay un ejemplo de lo que hicimos al tratar de obtener un valor de un DB que podría no estar allí.

 @Component public class FindID extends MappingSqlQuery { @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); } 

En el DAO, simplemente llamamos …

 Long id = findID.findObject(id); 

No es claro en rendimiento, pero funciona y es limpio.

Para Byron, puedes probar esto …

 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 li = jdbcTemplate.queryForList(sql,String.class); return li.get(0).toString(); } 

para hacer

  jdbcTemplate.queryForList(sql, String.class) 

trabaje, asegúrese de que su jdbcTemplate sea de tipo

  org.springframework.jdbc.core.JdbcTemplate 

Podemos usar query en lugar de queryForObject, la principal diferencia entre query y queryForObject es esa lista de retorno de consulta de Object (basada en el tipo de retorno del asignador Row) y esa lista puede estar vacía si no se reciben datos desde la base de datos mientras que queryForObject siempre espera que solo un objeto sea extraído de db ni nulo ni varias filas y en caso de que si el resultado esté vacío, queryForObject arroje EmptyResultDataAccessException, escribí un código con la consulta que resolverá el problema de EmptyResultDataAccessException en caso de resultado nulo.

 ---------- public UserInfo getUserInfo(String username, String password) { String sql = "SELECT firstname, lastname,address,city FROM users WHERE id=? and pass=?"; List userInfoList = jdbcTemplate.query(sql, new Object[] { username, password }, new RowMapper() { 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); } } 

Solo capté esta “EmptyResultDataAccessException”

 public Myclass findOne(String id){ try { Myclass m = this.jdbcTemplate.queryForObject( "SELECT * FROM tb_t WHERE id = ?", new Object[]{id}, new RowMapper() { 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; } } 

entonces puedes verificar:

 if(m == null){ // insert operation. }else{ // update operation. }