web-dev-qa-db-fra.com

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

J'utilise Jdbctemplate pour récupérer une seule valeur de chaîne à 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 un résultat positif 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 semblerait que je devrais simplement récupérer un zéro au lieu de lancer une exception. Comment puis-je réparer cela? Merci d'avance.

87
Byron

Dans JdbcTemplate, queryForInt, queryForLong, queryForObject, toutes ces méthodes prévoient que la requête exécutée renverra une et une seule ligne. Si vous n'obtenez aucune ligne ou plus d'une ligne, cela entraînera IncorrectResultSizeDataAccessException. Maintenant, le moyen correct 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 ce n'est pas possible, utilisez plutôt la méthode 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.    
}
153
Rakesh Juyal

Vous pouvez également utiliser un ResultSetExtractor au lieu d'un RowMapper . Les deux sont aussi faciles 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;
        }
    });
}

La ResultSetExtractor présente l'avantage supplémentaire de pouvoir gérer tous les cas où plusieurs lignes sont renvoyées ou aucune.

UPDATE: Cela fait plusieurs années et j'ai quelques astuces à partager. JdbcTemplate fonctionne parfaitement avec les lambda Java 8 auxquels les exemples suivants sont conçus, mais vous pouvez très facilement utiliser une classe statique pour obtenir le même résultat.

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

Tout d'abord. Supposons que vous ayez un objet compte avec deux propriétés pour simplifier Account(Long id, String name). Vous souhaiteriez probablement avoir une RowMapper pour cet objet de 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 objets de domaine Account à partir d'une requête (jt est une instance JdbcTemplate).

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

Génial, mais maintenant nous voulons notre problème initial 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);
}

Bien, mais c’est un schéma que vous pouvez et voudrez répéter. Vous pouvez donc créer une méthode générique pour créer un nouveau ResultSetExtractor _ pour la tâche.

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

La création d'une ResultSetExtractor devient maintenant triviale.

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 maintenant très facilement combiner des pièces de manière puissante pour simplifier votre domaine.

UPDATE 2: combiner avec un facultatif pour des 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 utilisé pourrait avoir les caractéristiques suivantes:

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);
}
31
Brett Ryan

Ce n'est pas une bonne solution car vous vous basez sur des exceptions pour contrôler le flux. Dans votre solution, il est normal d'avoir 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);
    }
}
19

En fait, vous pouvez jouer avec JdbcTemplate et personnaliser votre propre méthode selon vos préférences. Ma suggestion est de faire quelque chose comme ça:

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 le jdbc.queryForObject original, mais sans throw new EmptyResultDataAccessException quand size == 0

7
Alex

Depuis que je retourne un null quand il n'y a pas de données, c'est quelque chose que je veux faire souvent lorsque j'utilise queryForObject, il m'a été utile d'étendre JdbcTemplate et d'ajouter une méthode queryForNullableObject similaire à celle décrite 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 utiliser cela dans votre code de la même manière que vous utilisiez 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?

7
Stewart Evans

Ok, je l'ai compris. Je viens de l'envelopper dans un catch try et renvoyer 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;
    }
5
Byron

Vous pouvez utiliser une fonction group afin que votre requête retourne toujours un résultat .

MIN(ID_NMB_SRZ)
2
DS.

Depuis getJdbcTemplate (). QueryForMap attend une taille minimale de un, mais lorsqu'il renvoie null, il indique 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;
}
1

Dans Postgres, vous pouvez faire en sorte que presque toute requête à valeur unique renvoie une valeur ou une valeur null en l'enveloppant:

SELECT (SELECT <query>) AS value

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

1
Rich

En utilisant Java 8 ou une version ultérieure, vous pouvez utiliser un Optional et Java Streams.

Ainsi, vous pouvez simplement utiliser la méthode JdbcTemplate.queryForList(), créer un flux et utiliser Stream.findFirst() qui retournera la première valeur du flux ou un Optional vide:

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. Ainsi, pas plus d'un élément n'est transféré de la base de données.

0
Samuel Philipp

Nous pouvons utiliser query au lieu de queryForObject, la différence majeure entre query et queryForObject est que la liste de retour de requête de Object (basée sur le type de retour Row mapper) et que cette liste peut être vide si aucune donnée n'est reçue de la base de données, alors que queryForObject attend toujours qu'un seul objet extraite de la base de données ni aucune ligne ni plusieurs lignes et dans le cas où le résultat est vide, queryForObject lève une exception EmptyResultDataAccessException. J'avais écrit un code à l'aide d'une requête qui résolvait 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);
      }
  }
0
ABHAY JOHRI

faire 

    jdbcTemplate.queryForList(sql, String.class)

travail, assurez-vous que votre jdbcTemplate est de type 

    org.springframework.jdbc.core.JdbcTemplate
0
Dmitry

Je m'occupais de ça 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 lorsque nous avons tenté d’obtenir une valeur d’une base de données qui n’y figurait peut-être pas.

@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 la performance, mais ça marche et c'est bien.

0
grbonk

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