Requête Jdbctemplate pour la chaîne: EmptyResultDataAccessException: taille de résultat incorrecte: attendue 1, réelle 0

105

J'utilise Jdbctemplate pour récupérer une seule valeur String à partir de la base de données. Voici ma méthode.

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

Dans mon scénario, il est tout à fait possible de ne PAS obtenir de réponse à ma requête, donc ma question est de savoir comment contourner le message d'erreur suivant.

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

Il me semble que je devrais simplement récupérer un null au lieu de lancer une exception. Comment puis-je réparer cela? Merci d'avance.

Byron
la source

Réponses:

179

En JdbcTemplate, queryForInt, queryForLong, queryForObjecttoutes ces méthodes ESPÈRE que requête exécutée renverront une et une seule ligne. Si vous n'obtenez aucune ligne ou plus d'une ligne, cela entraînera IncorrectResultSizeDataAccessException. Maintenant, la manière correcte n'est pas d'attraper cette exception ou EmptyResultDataAccessException, mais assurez-vous que la requête que vous utilisez ne doit renvoyer qu'une seule ligne. Si cela n'est pas possible, utilisez la queryméthode à la place.

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.    
}
Rakesh Juyal
la source
Comme mentionné ci-dessous, le seul inconvénient ici est que si le type de retour était un type complexe, vous construiriez plusieurs objets et instancier une liste, également ResultSet.next()appelé inutilement. L'utilisation d'un ResultSetExtractorest un outil beaucoup plus efficace dans ce cas.
Brett Ryan
3
Il manque des parenthèses dans la définition de classe anonyme - nouveau RowMapper ()
Janis Koluzs
Je suis avec Brett là-dessus. ResultSetExtractor est plus propre :)
laher
2
Salut @Rakesh, pourquoi non seulement return nulldans catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia
1
Hey! Puis-je simplement demander pourquoi «Maintenant, la bonne façon est de ne pas intercepter cette exception», en considérant si vous utilisez queryForObject? Quel serait le problème avec la capture d'une exception dans le cas de queryForObject? Merci :)
Michael Stokes
48

Vous pouvez également utiliser un ResultSetExtractorau lieu d'un RowMapper. Les deux sont aussi simples l'un que l'autre, la seule différence est que vous appelez 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;
        }
    });
}

Le ResultSetExtractora l'avantage supplémentaire que vous pouvez gérer tous les cas où il y a plus d'une ligne ou aucune ligne renvoyée.

MISE À JOUR : Plusieurs années plus tard et j'ai quelques astuces à partager. JdbcTemplatefonctionne parfaitement avec java 8 lambdas pour lesquels les exemples suivants sont conçus, mais vous pouvez assez facilement utiliser une classe statique pour obtenir la même chose.

Bien que la question porte sur les types simples, ces exemples servent de guide pour le cas courant de l'extraction d'objets de domaine.

Tout d'abord. Supposons que vous ayez un objet compte avec deux propriétés pour plus de simplicité Account(Long id, String name). Vous souhaiteriez probablement avoir un RowMapperobjet pour ce domaine.

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

Vous pouvez maintenant utiliser ce mappeur directement dans une méthode pour mapper des Accountobjets de domaine à partir d'une requête ( jtest une JdbcTemplateinstance).

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

Excellent, mais maintenant nous voulons notre problème d'origine et nous utilisons ma solution originale en réutilisant le RowMapperpour 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 en créer une nouvelle ResultSetExtractorpour la tâche.

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

Créer un ResultSetExtractormaintenant devient trivial.

private static final ResultSetExtractor<Account> EXTRACTOR_ACCOUNT =
        singletonExtractor(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 désormais combiner assez facilement des parties de manière puissante pour simplifier votre domaine.

MISE À JOUR 2 : À combiner avec une option facultative pour les valeurs facultatives au lieu de null.

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

Qui maintenant, lorsqu'il est utilisé, pourrait avoir les éléments suivants:

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);
}
Brett Ryan
la source
21

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

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);
    }
}
Philippe Marschall
la source
ma solution n'est peut-être pas la plus élégante mais au moins la mienne fonctionne. Vous donnez un exemple de queryForObjectList qui n'est même pas une option avec Jdbctemplate.
Byron
1
Le seul inconvénient ici est que si le type de retour était un type complexe, vous construiriez plusieurs objets et instancier une liste, également ResultSet.next()appelé inutilement. L'utilisation d'un ResultSetExtractorest un outil beaucoup plus efficace dans ce cas.
Brett Ryan
et si n'avoir aucune valeur est une option, mais n'en avoir pas plus d'une? J'ai souvent ce modèle et j'aimerais avoir un queryForOptionalObject au printemps à cet effet.
Guillaume
7

Ok, je l'ai compris. Je l'ai juste enveloppé dans une capture d'essai et renvoyé 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;
    }
Byron
la source
1
Je ne vois pas pourquoi c'est si mauvais et pourquoi vous avez reçu autant de votes négatifs pour cela, à part pour être un fondamentaliste sur le principe «pas de flux de programme dans l'exception». J'aurais juste remplacé la trace de la pile d'impression par un commentaire expliquant le cas et ne rien faire d'autre.
Guillaume
7

En fait, vous pouvez jouer avec JdbcTemplateet personnaliser votre propre méthode à votre guise. Ma suggestion est de faire quelque chose comme ceci:

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

Cela fonctionne comme l'original jdbc.queryForObject, mais sans throw new EmptyResultDataAccessExceptionquand size == 0.

Alex
la source
@Abdull UserMapper implements RowMapper<String>.
Brett Ryan
Je pense que c'est la meilleure réponse ici car elle donne la syntaxe la plus courte
Stan Sokolov
DataAccessUtils.singleResult(...)c'est ce que je cherchais. Thx
Drakes
7

Depuis renvoyer une valeur null quand 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'ajouter une méthode queryForNullableObject similaire à ci-dessous.

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

}

Vous pouvez maintenant l'utiliser dans votre code de la même manière que vous avez utilisé queryForObject

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

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

Stewart Evans
la source
1
Ça l'est, et ça devrait être au printemps
Guillaume
4

En utilisant Java 8 ou supérieur, vous pouvez utiliser un Optionalet Java Streams.

Vous pouvez donc simplement utiliser la JdbcTemplate.queryForList()méthode, créer un Stream et utiliser Stream.findFirst()qui renverra la première valeur du Stream ou un vide 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();
}

Pour améliorer les performances de la requête, vous pouvez ajouter LIMIT 1à votre requête, afin que pas plus d'un élément ne soit transféré de la base de données.

Samuel Philipp
la source
1
Agréable et propre. Pas de ifs ni de lambdas supplémentaires. Je l'aime.
BeshEater
2

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

MIN(ID_NMB_SRZ)
DS.
la source
1

Dans Postgres, vous pouvez faire en sorte que presque n'importe quelle requête à valeur unique renvoie une valeur ou une valeur nulle en l'enveloppant:

SELECT (SELECT <query>) AS value

et évitez ainsi la complexité de l'appelant.

Riches
la source
1

Depuis getJdbcTemplate (). QueryForMap attend une taille minimale de un, mais quand il retourne null, il affiche EmptyResultDataAccesso fix dis quand peut utiliser la logique ci-dessous

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;
}
Mahesh Jayachandran
la source
0

J'avais traité de cela avant et j'avais posté dans les forums de printemps.

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 en essayant d'obtenir une valeur d'une base de données qui pourrait ne pas être là.

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

Dans le DAO, nous appelons simplement ...

Long id = findID.findObject(id);

Pas clair sur les performances, mais cela fonctionne et est soigné.

grbonk
la source
0

Pour Byron, vous pouvez essayer ceci.

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();
        }
Mohan Kumar Dg
la source
0

faire

    jdbcTemplate.queryForList(sql, String.class)

travail, assurez-vous que votre jdbcTemplate est de type

    org.springframework.jdbc.core.JdbcTemplate
Dmitry
la source
0

Nous pouvons utiliser query au lieu de queryForObject, la principale différence entre query et queryForObject est que la liste de retour de requête de 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 la base de données tandis que queryForObject attend toujours qu'un seul objet soit extrait de la base de données ni null ni plusieurs lignes et au cas où le résultat est vide, queryForObject jette EmptyResultDataAccessException, j'avais écrit un code en utilisant une requête qui surmontera le problème de EmptyResultDataAccessException en cas de résultat nul.

----------


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);
      }
  }
ABHAY JOHRI
la source
0

IMHO renvoyer un nullest une mauvaise solution parce que maintenant vous avez le problème de l'envoyer et de l'interpréter au (probable) client frontal. J'ai eu la même erreur et je l'ai résolue en renvoyant simplement un fichier List<FooObject>. J'ai utilisé JDBCTemplate.query().

Au front end (client web angulaire), j'examine simplement la liste et si elle est vide (de longueur nulle), je la traite comme aucun enregistrement trouvé.

commejudo
la source
-1

J'attrape juste cette "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;
    }
}

alors vous pouvez vérifier:

if(m == null){
    // insert operation.
}else{
    // update operation.
}
Tourbillon
la source
Nous pouvons utiliser query au lieu de
queryForObject
1
Habituellement considéré comme une mauvaise pratique d'abuser de telles exceptions. Les exceptions ne concernent pas le flux logique du programme prévisible, mais des situations exceptionnelles.
Chris Baker