Jdbctemplate query per la stringa: EmptyResultDataAccessException: dimensione del risultato non corretta: previsto 1, effettivo 0


105

Sto usando Jdbctemplate per recuperare un singolo valore String dal db. Ecco il mio metodo.

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

Nel mio scenario è possibile NON ottenere un riscontro sulla mia query, quindi la mia domanda è come aggirare il seguente messaggio di errore.

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

Mi sembra che dovrei semplicemente recuperare un null invece di lanciare un'eccezione. Come posso risolvere questo problema? Grazie in anticipo.

Risposte:


179

In JdbcTemplate, queryForInt, queryForLong, queryForObjecttutti i metodi tali aspetta che query eseguita torneranno una e una sola riga. Se non ottieni righe o più di una riga, il risultato sarà IncorrectResultSizeDataAccessException. Ora il modo corretto è non catturare questa eccezione o EmptyResultDataAccessException, ma assicurati che la query che stai usando restituisca solo una riga. Se non è possibile, usa queryinvece il metodo.

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

Come accennato di seguito, l'unico inconveniente qui è che se il tipo restituito fosse un tipo complesso, si costruiranno più oggetti e si creerà un'istanza di un elenco, inoltre ResultSet.next()verrebbe chiamato inutilmente. L'utilizzo di a ResultSetExtractorè uno strumento molto più efficiente in questo caso.
Brett Ryan

3
Mancano parentesi nella definizione della classe anonima - nuovo RowMapper ()
Janis Koluzs

Sono con Brett su questo. ResultSetExtractor è più pulito :)
laher

2
Ciao @ Rakesh, perché non solo return nullin catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia

1
Hey! Posso solo chiedere perché "Ora il modo corretto è non catturare questa eccezione", considerando se stai usando queryForObject? Cosa ci sarebbe di sbagliato nel rilevare un'eccezione nel caso di queryForObject? Grazie :)
Michael Stokes

48

Puoi anche usare un ResultSetExtractorinvece di un RowMapper. Entrambi sono altrettanto facili l'uno dell'altro, l'unica differenza è che chiami 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;
        }
    });
}

L' ResultSetExtractorha il vantaggio che è possibile gestire tutti i casi in cui ci sono più di una riga o nessun righe restituite.

AGGIORNAMENTO : Sono passati diversi anni e ho alcuni trucchi da condividere. JdbcTemplatefunziona in modo eccellente con java 8 lambda per i quali sono progettati i seguenti esempi, ma puoi facilmente usare una classe statica per ottenere lo stesso risultato.

Sebbene la domanda riguardi i tipi semplici, questi esempi servono come guida per il caso comune di estrazione di oggetti di dominio.

Prima di tutto. Supponiamo di avere un oggetto account con due proprietà per semplicità Account(Long id, String name). Probabilmente vorresti avere un RowMapperper questo oggetto di dominio.

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

È ora possibile utilizzare questo mappatore direttamente all'interno di un metodo per mappare gli Accountoggetti del dominio da una query ( jtè JdbcTemplateun'istanza).

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

Ottimo, ma ora vogliamo il nostro problema originale e usiamo la mia soluzione originale riutilizzando il RowMapperper eseguire la mappatura per noi.

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

Ottimo, ma questo è uno schema che potresti e vorresti ripetere. Quindi puoi creare un metodo factory generico per crearne uno nuovo ResultSetExtractorper l'attività.

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

Creare un ResultSetExtractoradesso diventa banale.

private static final ResultSetExtractor<Account> EXTRACTOR_ACCOUNT =
        singletonExtractor(MAPPER_ACCOUNT);

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

Spero che questo aiuti a dimostrare che ora puoi combinare abbastanza facilmente le parti in un modo potente per rendere il tuo dominio più semplice.

AGGIORNAMENTO 2 : Combina con un facoltativo per i valori facoltativi invece di null.

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

Che ora, se utilizzato, potrebbe avere quanto segue:

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

Non è una buona soluzione perché ti affidi alle eccezioni per il flusso di controllo. Nella tua soluzione è normale ricevere eccezioni, è normale averle nel 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<String> certs = jdbc.queryForList(sql, String.class); 
    if (certs.isEmpty()) {
        return null;
    } else {
        return certs.get(0);
    }
}

la mia soluzione potrebbe non essere la più elegante ma almeno la mia funziona. Fornisci un esempio di queryForObjectList che non è nemmeno un'opzione con Jdbctemplate.
Byron

1
L'unico svantaggio qui è che se il tipo restituito fosse un tipo complesso, si creerebbero più oggetti e si istanzerà un elenco, inoltre ResultSet.next()verrebbe chiamato inutilmente. L'utilizzo di a ResultSetExtractorè uno strumento molto più efficiente in questo caso.
Brett Ryan

e se non avere valore fosse un'opzione, ma non averne più di uno? Ho spesso questo modello e vorrei avere una queryForOptionalObject in primavera per questo scopo.
Guillaume

7

Ok, ho capito. L'ho appena inserito in un tentativo di cattura e rispedito 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
Non vedo perché questo sia così negativo e perché hai ricevuto così tanti voti negativi per questo, tranne per essere un fondamentalista sul principio "nessun flusso di programma entro eccezioni". Avrei semplicemente sostituito la traccia di printstack con un commento che spiegasse il caso e non avrei fatto altro.
Guillaume

7

In realtà, puoi giocare JdbcTemplatee personalizzare il tuo metodo come preferisci. Il mio suggerimento è di creare qualcosa del genere:

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

Funziona come l'originale jdbc.queryForObject, ma senza throw new EmptyResultDataAccessExceptionquando size == 0.


@ Abdull UserMapper implements RowMapper<String>.
Brett Ryan

Penso che sia la risposta migliore qui in quanto fornisce la sintassi più breve
Stan Sokolov

DataAccessUtils.singleResult(...)è quello che stavo cercando. Thx
Drakes

7

Poiché la restituzione di un null quando non ci sono dati è qualcosa che voglio fare spesso quando si utilizza queryForObject, ho trovato utile estendere JdbcTemplate e aggiungere un metodo queryForNullableObject simile al seguente.

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

}

Ora puoi usarlo nel tuo codice nello stesso modo in cui hai usato queryForObject

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

Sarei interessato a sapere se qualcun altro pensa che questa sia una buona idea?


1
Lo è, e dovrebbe essere in primavera
Guillaume

4

Utilizzando Java 8 o versioni successive è possibile utilizzare Optionale Java Streams.

Quindi puoi semplicemente utilizzare il JdbcTemplate.queryForList()metodo, creare uno Stream e utilizzare Stream.findFirst()che restituirà il primo valore dello Stream o un vuoto 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();
}

Per migliorare le prestazioni della query è possibile aggiungere LIMIT 1alla query, in modo che non venga trasferito più di 1 elemento dal database.


1
Bello e pulito. Nessun if o lambda aggiuntivi. Mi piace.
BeshEater

2

È possibile utilizzare una funzione di gruppo in modo che la query restituisca sempre un risultato. vale a dire

MIN(ID_NMB_SRZ)

1

In Postgres, puoi fare in modo che quasi tutte le query a valore singolo restituiscano un valore o un valore nullo avvolgendolo:

SELECT (SELECT <query>) AS value

e quindi evitare la complessità nel chiamante.


1

Poiché getJdbcTemplate (). QueryForMap si aspetta una dimensione minima di uno ma quando restituisce null mostra la correzione di EmptyResultDataAccesso quando è possibile utilizzare la logica sottostante

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

Mi sono occupato di questo prima e avevo postato nei forum di primavera.

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

Il consiglio che abbiamo ricevuto è stato quello di utilizzare un tipo di SQlQuery. Ecco un esempio di quello che abbiamo fatto quando abbiamo cercato di ottenere un valore da un DB che potrebbe non essere presente.

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

Nel DAO quindi chiamiamo solo ...

Long id = findID.findObject(id);

Non è chiaro sulle prestazioni, ma funziona ed è pulito.


0

Per Byron, puoi provare questo ..

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

produrre

    jdbcTemplate.queryForList(sql, String.class)

lavoro, assicurati che il tuo jdbcTemplate sia di tipo

    org.springframework.jdbc.core.JdbcTemplate

0

Possiamo usare query invece di queryForObject, la principale differenza tra query e queryForObject è che l'elenco di restituzione della query di Object (basato sul tipo restituito del mappatore di righe) e quell'elenco può essere vuoto se nessun dato viene ricevuto dal database mentre queryForObject si aspetta sempre solo un singolo oggetto recuperato da db né null né più righe e nel caso in cui il risultato è vuoto, queryForObject genera EmptyResultDataAccessException, avevo scritto un codice utilizzando query che supererà il problema di EmptyResultDataAccessException in caso di risultato nullo.

----------


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 restituire a nullè una cattiva soluzione perché ora hai il problema di inviarlo e interpretarlo al (probabile) client front-end. Ho avuto lo stesso errore e l'ho risolto restituendo semplicemente un file List<FooObject>. Ho usato JDBCTemplate.query().

Al front-end (client web angolare), esamino semplicemente l'elenco e se è vuoto (di lunghezza zero), lo tratto come nessun record trovato.


-1

Ho appena catturato questa "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;
    }
}

quindi puoi controllare:

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

Possiamo usare query invece di queryForObject
ABHAY JOHRI

1
Solitamente considerato una cattiva pratica abusare di eccezioni come questa. Le eccezioni non riguardano un flusso logico di programma prevedibile, ma situazioni eccezionali.
Chris Baker
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.