Jdbctemplate-Abfrage für Zeichenfolge: EmptyResultDataAccessException: Falsche Ergebnisgröße: erwartete 1, tatsächliche 0

105

Ich verwende Jdbctemplate, um einen einzelnen String-Wert aus der Datenbank abzurufen. Hier ist meine Methode.

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

In meinem Szenario ist es durchaus möglich, dass meine Anfrage NICHT getroffen wird. Meine Frage lautet also, wie ich die folgende Fehlermeldung umgehen kann.

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

Es scheint mir, dass ich einfach eine Null zurückbekommen sollte, anstatt eine Ausnahme auszulösen. Wie kann ich das beheben? Danke im Voraus.

Byron
quelle

Antworten:

179

In JdbcTemplate, queryForInt, queryForLong, queryForObjectalle diese Methoden erwartet , dass Abfrage ausgeführt wird man zurückkehren und nur eine Zeile. Wenn Sie keine oder mehr als eine Zeile erhalten, führt dies zu IncorrectResultSizeDataAccessException. Der richtige Weg besteht nun darin, diese Ausnahme nicht abzufangen oder EmptyResultDataAccessExceptionsicherzustellen, dass die von Ihnen verwendete Abfrage nur eine Zeile zurückgibt. Wenn es überhaupt nicht möglich ist, verwenden Sie querystattdessen die Methode.

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
quelle
Wie unten erwähnt, besteht der einzige Nachteil hier darin, dass, wenn der Rückgabetyp ein komplexer Typ wäre, Sie mehrere Objekte erstellen und eine Liste instanziieren ResultSet.next()würden, dies ebenfalls unnötig aufgerufen würde. Die Verwendung von a ResultSetExtractorist in diesem Fall ein viel effizienteres Werkzeug.
Brett Ryan
3
In der anonymen Klassendefinition fehlen Klammern - neuer RowMapper ()
Janis Koluzs
Ich bin mit Brett dabei. ResultSetExtractor ist sauberer :)
laher
2
Hallo @ Rakesh, warum nicht nur return nullin catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia
1
Hallo! Kann ich nur fragen, warum "Jetzt ist es nicht richtig, diese Ausnahme abzufangen", wenn Sie überlegen, ob Sie queryForObject verwenden? Was wäre falsch daran, im Fall von queryForObject eine Ausnahme abzufangen? Danke :)
Michael Stokes
48

Sie können auch a ResultSetExtractoranstelle von a verwenden RowMapper. Beide sind genauso einfach wie die anderen, der einzige Unterschied ist, dass Sie anrufen 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;
        }
    });
}

Das ResultSetExtractor hat den zusätzlichen Vorteil, dass Sie alle Fälle behandeln können, in denen mehr als eine Zeile oder keine Zeilen zurückgegeben werden.

UPDATE : Einige Jahre später habe ich ein paar Tricks zu teilen.JdbcTemplatefunktioniert hervorragend mit Java 8 Lambdas, für die die folgenden Beispiele entwickelt wurden, aber Sie können ganz einfach eine statische Klasse verwenden, um dasselbe zu erreichen.

Während es sich um einfache Typen handelt, dienen diese Beispiele als Leitfaden für den allgemeinen Fall des Extrahierens von Domänenobjekten.

Zuerst. Nehmen wir an, Sie haben der Einfachheit halber ein Kontoobjekt mit zwei Eigenschaften Account(Long id, String name). Sie möchten wahrscheinlich ein RowMapperObjekt für diese Domain haben.

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

Sie können diesen Mapper jetzt direkt in einer Methode verwenden, um AccountDomänenobjekte aus einer Abfrage ( jtist eine JdbcTemplateInstanz) zuzuordnen .

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

Großartig, aber jetzt wollen wir unser ursprüngliches Problem und verwenden meine ursprüngliche Lösung RowMapper, um das Mapping für uns durchzuführen.

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

Großartig, aber dies ist ein Muster, das Sie möglicherweise und möglicherweise wiederholen möchten. Sie können also eine generische Factory-Methode erstellen, um eine neue ResultSetExtractorfür die Aufgabe zu erstellen .

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

Das Erstellen eines ResultSetExtractorJetzt wird trivial.

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

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

Ich hoffe, dies hilft zu zeigen, dass Sie Teile jetzt ganz einfach auf leistungsstarke Weise kombinieren können, um Ihre Domain einfacher zu gestalten.

UPDATE 2 : Kombinieren Sie mit einem optional für optionale Werte statt null.

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

Was jetzt, wenn es verwendet wird, Folgendes haben könnte:

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
quelle
21

Dies ist keine gute Lösung, da Sie sich auf Ausnahmen für den Kontrollfluss verlassen. In Ihrer Lösung ist es normal, Ausnahmen zu erhalten, es ist normal, sie im Protokoll zu haben.

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
quelle
Meine Lösung ist vielleicht nicht die eleganteste, aber zumindest meine. Sie geben ein Beispiel für queryForObjectList, das mit Jdbctemplate nicht einmal eine Option ist.
Byron
1
Der einzige Nachteil hierbei ist, dass wenn der Rückgabetyp ein komplexer Typ wäre, Sie mehrere Objekte erstellen und eine Liste instanziieren ResultSet.next()würden , was ebenfalls unnötig aufgerufen würde. Die Verwendung von a ResultSetExtractorist in diesem Fall ein viel effizienteres Werkzeug.
Brett Ryan
und was ist, wenn es eine Option ist, keinen Wert zu haben, aber nicht mehr als einen? Ich habe dieses Muster oft und möchte zu diesem Zweck ein queryForOptionalObject in Spring haben.
Guillaume
7

Tatsächlich können Sie mit JdbcTemplateIhrer eigenen Methode spielen und diese nach Ihren Wünschen anpassen. Mein Vorschlag ist, so etwas zu machen:

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

Es funktioniert wie das Original jdbc.queryForObject, aber ohne throw new EmptyResultDataAccessExceptionwann size == 0.

Alex
quelle
@ Abdull UserMapper implements RowMapper<String>.
Brett Ryan
Ich denke, es ist die beste Antwort hier, da es die kürzeste Syntax gibt
Stan Sokolov
DataAccessUtils.singleResult(...)ist das, wonach ich gesucht habe. Thx
Drakes
7

Da ich bei der Verwendung von queryForObject häufig eine Null zurückgeben möchte, wenn keine Daten vorhanden sind, habe ich es als nützlich empfunden, JdbcTemplate zu erweitern und eine queryForNullableObject-Methode hinzuzufügen, die der folgenden ähnelt.

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

}

Sie können dies jetzt in Ihrem Code genauso verwenden, wie Sie queryForObject verwendet haben

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

Es würde mich interessieren, ob jemand anderes dies für eine gute Idee hält.

Stewart Evans
quelle
1
Es ist, und es sollte im Frühling sein
Guillaume
6

Ok, ich habe es herausgefunden. Ich habe es einfach in einen Try Catch eingewickelt und null zurückgeschickt.

    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
quelle
1
Ich verstehe nicht, warum das so schlimm ist und warum Sie so viel Gegenstimme dafür erhalten haben, abgesehen davon, dass Sie ein Fundamentalist für das Prinzip „Kein Programmfluss innerhalb einer Ausnahme“ sind. Ich hätte einfach die Printstack-Ablaufverfolgung durch einen Kommentar ersetzt, der den Fall erklärt, und nichts anderes getan.
Guillaume
4

Mit Java 8 oder höher können Sie einen Optionalund Java-Streams verwenden.

Sie können also einfach die JdbcTemplate.queryForList()Methode verwenden, einen Stream erstellen und verwenden, Stream.findFirst()der den ersten Wert des Streams oder einen leeren Wert zurückgibt 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();
}

Um die Leistung der Abfrage zu verbessern, können Sie sie LIMIT 1an Ihre Abfrage anhängen , sodass nicht mehr als 1 Element aus der Datenbank übertragen wird.

Samuel Philipp
quelle
Schön und sauber. Keine zusätzlichen Wenns oder Lambdas. Ich mag das.
BeshEater vor
2

Sie können eine Gruppenfunktion verwenden, damit Ihre Abfrage immer ein Ergebnis zurückgibt. dh

MIN(ID_NMB_SRZ)
DS.
quelle
1

In Postgres können Sie fast jede einzelne Wertabfrage dazu bringen, einen Wert oder eine Null zurückzugeben, indem Sie sie einschließen:

SELECT (SELECT <query>) AS value

und damit Komplexität im Anrufer vermeiden.

Reich
quelle
1

Da getJdbcTemplate (). QueryForMap eine Mindestgröße von eins erwartet, aber wenn es null zurückgibt, wird EmptyResultDataAccesso angezeigt, um zu beheben, wann die folgende Logik verwendet werden kann

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
quelle
0

Ich habe mich vorher damit befasst und in den Frühlingsforen gepostet.

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

Der Rat, den wir erhielten, war die Verwendung einer Art SQlQuery. Hier ist ein Beispiel dafür, was wir getan haben, als wir versucht haben, einen Wert aus einer Datenbank herauszuholen, der möglicherweise nicht vorhanden ist.

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

Im DAO rufen wir dann einfach an ...

Long id = findID.findObject(id);

Nicht klar über die Leistung, aber es funktioniert und ist ordentlich.

grbonk
quelle
0

Für Byron können Sie dies versuchen ..

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
quelle
0

zu machen

    jdbcTemplate.queryForList(sql, String.class)

Stellen Sie sicher, dass Ihre jdbcTemplate vom Typ ist

    org.springframework.jdbc.core.JdbcTemplate
Dmitry
quelle
0

Wir können query anstelle von queryForObject verwenden. Der Hauptunterschied zwischen query und queryForObject besteht darin, dass die Abfragerückgabeliste des Objekts (basierend auf dem Row-Mapper-Rückgabetyp) leer ist und diese Liste leer sein kann, wenn keine Daten von der Datenbank empfangen werden, während queryForObject immer nur ein einzelnes Objekt erwartet Abgerufen von db weder null noch mehrere Zeilen und falls das Ergebnis leer ist, löst queryForObject EmptyResultDataAccessException aus. Ich hatte einen Code mit Abfrage geschrieben, der das Problem der EmptyResultDataAccessException im Falle eines Null-Ergebnisses löst.

----------


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
quelle
0

IMHO ist die Rückgabe von a nulleine schlechte Lösung, da Sie jetzt das Problem haben, sie auf dem (wahrscheinlichen) Front-End-Client zu senden und zu interpretieren. Ich hatte den gleichen Fehler und löste ihn, indem ich einfach a zurückgab List<FooObject>. Ich habe benutzt JDBCTemplate.query().

Am Frontend (Angular Web Client) überprüfe ich einfach die Liste und wenn sie leer ist (Länge Null), behandle ich sie als keine Datensätze gefunden.

Likejudo
quelle
-1

Ich habe gerade diese "EmptyResultDataAccessException" abgefangen.

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

dann können Sie überprüfen:

if(m == null){
    // insert operation.
}else{
    // update operation.
}
Eddy
quelle
Wir können Abfrage anstelle von queryForObject
ABHAY JOHRI
1
Wird normalerweise als schlechte Praxis angesehen, um solche Ausnahmen zu missbrauchen. Ausnahmen gelten nicht für vorhersehbare Programmlogikabläufe, sondern für Ausnahmesituationen.
Chris Baker