Requête Jdbctemplate pour la chaîne: EmptyResultDataAccessException: Taille du résultat incorrecte: attendu 1, réel 0

J’utilise Jdbctemplate pour récupérer une seule valeur de chaîne à partir de la firebase database. Voici ma méthode

public Ssortingng test() { Ssortingng cert=null; Ssortingng sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'"; cert = (Ssortingng) jdbc.queryForObject(sql, Ssortingng.class); return cert; } 

Dans mon scénario, il est tout à fait possible de ne pas obtenir un hit sur ma requête. Ma question est donc de savoir comment contourner le message d’erreur suivant.

 EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0 

Il me semble que je devrais juste récupérer une null plutôt que de lancer une exception. Comment puis-je réparer cela? Merci d’avance.

Dans JdbcTemplate, queryForInt, queryForLong, queryForObject toutes ces méthodes attendent que la requête exécutée renvoie une et une seule ligne. Si vous n’obtenez aucune ligne ou plus d’une ligne, cela entraînera une exception IncorrectResultSizeDataAccessException . La méthode correcte consiste maintenant à ne pas intercepter cette exception ou cette exception EmptyResultDataAccessException , mais assurez-vous que la requête que vous utilisez ne doit renvoyer qu’une seule ligne. Si ce n’est pas du tout possible, utilisez plutôt la méthode de query .

 List strLst = getJdbcTemplate().query(sql,new RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getSsortingng(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. } 

Vous pouvez également utiliser un ResultSetExtractor au lieu d’un RowMapper . Les deux sont aussi faciles les uns que les autres, la seule différence est que vous appelez ResultSet.next() .

 public Ssortingng test() { Ssortingng 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 Ssortingng extractData(ResultSet rs) throws SQLException, DataAccessException { return rs.next() ? rs.getSsortingng("ID_NMB_SRZ") : null; } }); } 

ResultSetExtractor présente l’avantage supplémentaire de pouvoir gérer tous les cas où il y a plus d’une ligne ou aucune ligne renvoyée.

MISE À JOUR : Plusieurs années après et j’ai quelques astuces à partager. JdbcTemplate fonctionne JdbcTemplate avec java 8 lambdas pour lequel les exemples suivants sont conçus, mais vous pouvez facilement utiliser une classe statique pour obtenir la même chose.

Bien que la question concerne des types simples, ces exemples servent de guide pour le cas courant d’extraction d’objects de domaine.

Tout d’abord. Supposons que vous ayez un object compte avec deux propriétés pour simplifier un Account(Long id, Ssortingng name) . Vous souhaiterez probablement avoir un RowMapper pour cet object de domaine.

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

Vous pouvez maintenant utiliser ce mappeur directement dans une méthode pour mapper des objects de domaine de Account partir d’une requête ( jt est une instance de JdbcTemplate ).

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

Génial, mais maintenant nous voulons notre problème original et nous utilisons ma solution originale en réutilisant le RowMapper pour effectuer le mappage pour nous.

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

Génial, mais c’est un modèle que vous pouvez et voudrez répéter. Vous pouvez donc créer une méthode de fabrique générique pour créer un nouveau ResultSetExtractor pour la tâche.

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

Créer un ResultSetExtractor devient désormais sortingvial.

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

J’espère que cela aidera à montrer que vous pouvez maintenant combiner facilement des pièces d’une manière puissante pour rendre votre domaine plus simple.

UPDATE 2 : Combinez avec un Facultatif pour les valeurs facultatives au lieu de null.

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

Ce qui maintenant lorsqu’il est utilisé pourrait avoir les éléments suivants:

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

Ce n’est pas une bonne solution, car vous comptez sur des exceptions pour le stream de contrôle. Dans votre solution, il est normal d’obtenir des exceptions, il est normal de les avoir dans le journal.

 public Ssortingng test() { Ssortingng 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, Ssortingng.class); if (certs.isEmpty()) { return null; } else { return certs.get(0); } } 

En fait, vous pouvez jouer avec JdbcTemplate et personnaliser votre propre méthode comme vous le souhaitez. Ma suggestion est de faire quelque chose comme ceci:

 public Ssortingng test() { Ssortingng cert = null; Ssortingng 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; } 

Il fonctionne comme l’original jdbc.queryForObject , mais sans throw new EmptyResultDataAccessException lorsque size == 0 .

Étant donné que renvoyer un null lorsqu’il n’y a pas de données est quelque chose que je veux faire souvent lors de l’utilisation de queryForObject, j’ai trouvé utile d’étendre JdbcTemplate et d’append une méthode queryForNullableObject similaire à celle ci-dessous.

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

Vous pouvez maintenant utiliser ceci dans votre code de la même façon que vous avez utilisé queryForObject

 Ssortingng result = queryForNullableObject(querySsortingng, Ssortingng.class); 

Je serais intéressé de savoir si quelqu’un d’autre pense que c’est une bonne idée?

Ok, je l’ai compris. Je l’ai juste emballé dans un try catch et renvoyé null.

  public Ssortingng test() { Ssortingng cert=null; Ssortingng 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 = (Ssortingng) jdbc.queryForObject(sql, Ssortingng.class); cert = (Ssortingng) o; } catch (EmptyResultDataAccessException e) { e.printStackTrace(); } return cert; } 

Vous pouvez utiliser une fonction de groupe pour que votre requête renvoie toujours un résultat. c’est à dire

 MIN(ID_NMB_SRZ) 

Dans Postgres, vous pouvez faire en sorte que presque toutes les requêtes de valeur unique renvoient une valeur ou une valeur null en l’encapsulant:

 SELECT (SELECT ) AS value 

et donc éviter la complexité dans l’appelant.

Depuis getJdbcTemplate (). QueryForMap attend une taille minimale de un mais quand elle renvoie null, cela indique que le correctif EmptyResultDataAccesso peut être utilisé sous la logique

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

Je me suis occupé de cela avant et avais posté dans les forums de spring.

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

Le conseil que nous avons reçu était d’utiliser un type de SQlQuery. Voici un exemple de ce que nous avons fait lorsque nous essayons d’obtenir une valeur à partir d’une firebase database qui pourrait ne pas exister.

 @Component public class FindID extends MappingSqlQuery { @Autowired public void setDataSource(DataSource dataSource) { Ssortingng sql = "Select id from address where id = ?"; super.setDataSource(dataSource); super.declareParameter(new SqlParameter(Types.VARCHAR)); super.setSql(sql); comstack(); } @Override protected Long mapRow(ResultSet rs, int rowNum) throws SQLException { return rs.getLong(1); } 

Dans le DAO, nous appelons simplement …

 Long id = findID.findObject(id); 

Pas clair sur la performance, mais ça marche et c’est soigné.

Pour Byron, vous pouvez essayer ceci.

 public Ssortingng test(){ Ssortingng 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,Ssortingng.class); return li.get(0).toSsortingng(); } 

faire

  jdbcTemplate.queryForList(sql, Ssortingng.class) 

travailler, assurez-vous que votre jdbcTemplate est de type

  org.springframework.jdbc.core.JdbcTemplate 

Nous pouvons utiliser query à la place de queryForObject, la principale différence entre query et queryForObject est cette liste de retour d’object (basée sur le type de retour du mappeur de lignes) et cette liste peut être vide si aucune donnée n’est reçue de queryForObject récupéré à partir de la firebase database ni null ni plusieurs lignes et dans le cas où result est vide alors queryForObject lève EmptyResultDataAccessException, j’avais écrit un code en utilisant la requête qui surmontera le problème de EmptyResultDataAccessException en cas de résultat nul.

 ---------- public UserInfo getUserInfo(Ssortingng username, Ssortingng password) { Ssortingng 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.getSsortingng("firstname")); user.setLastName(rs.getSsortingng("lastname")); user.setAddress(rs.getSsortingng("address")); user.setCity(rs.getSsortingng("city")); return user; } }); if (userInfoList.isEmpty()) { return null; } else { return userInfoList.get(0); } } 

Je viens d’attraper cette “EmptyResultDataAccessException”

 public Myclass findOne(Ssortingng 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.getSsortingng("name")); return m; } }); return m; } catch (EmptyResultDataAccessException e) { // result.size() == 0; return null; } } 

alors vous pouvez vérifier:

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