Zapytanie Jdbctemplate dla ciągu: EmptyResultDataAccessException: Niepoprawna wielkość wyniku: oczekiwana 1, rzeczywista 0


105

Używam Jdbctemplate do pobierania pojedynczej wartości ciągu z bazy danych. Oto moja metoda.

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

W moim scenariuszu jest całkowicie możliwe, aby NIE uzyskać trafienia w moim zapytaniu, więc moje pytanie brzmi, jak obejść następujący komunikat o błędzie.

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

Wydawałoby mi się, że powinienem po prostu odzyskać wartość zerową zamiast rzucać wyjątek. Jak mogę to naprawić? Z góry dziękuję.

Odpowiedzi:


179

W JdbcTemplate, queryForInt, queryForLong, queryForObjectwszystkie tego rodzaju metody przewiduje, że wykonane zapytanie będzie jeden i tylko jeden rząd. Jeśli nie otrzymasz żadnych wierszy lub więcej niż jeden wiersz, to spowoduje IncorrectResultSizeDataAccessException. Teraz poprawnym sposobem nie jest przechwytywanie tego wyjątku lub EmptyResultDataAccessException, ale upewnij się, że zapytanie, którego używasz, powinno zwracać tylko jeden wiersz. Jeśli w ogóle nie jest to możliwe, użyj querymetody zamiast tego.

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

Jak wspomniano poniżej, jedyną wadą jest to, że gdyby typ zwracany był typem złożonym, budowałbyś wiele obiektów i tworzyłby instancję listy, również ResultSet.next()byłby nazywany niepotrzebnie. ResultSetExtractorW tym przypadku użycie a jest znacznie wydajniejszym narzędziem.
Brett Ryan

3
Brakuje nawiasów w anonimowej definicji klasy - nowa RowMapper ()
Janis Koluzs,

Jestem w tej sprawie z Brettem. ResultSetExtractor jest czystszy :)
laher

2
Cześć @Rakesh, dlaczego nie tylko return nullw catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia

1
Hej! Czy mogę po prostu zapytać, dlaczego „Teraz właściwym sposobem nie jest przechwytywanie tego wyjątku”, biorąc pod uwagę, czy używasz queryForObject? Co byłoby złego w przechwytywaniu wyjątku w przypadku queryForObject? Dzięki :)
Michael Stokes

48

Możesz także użyć ResultSetExtractorzamiast a RowMapper. Oba są tak samo łatwe jak inne, jedyną różnicą jest to, że dzwonisz 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;
        }
    });
}

ResultSetExtractorMa dodatkową zaletę, że można obsługiwać wszystkie przypadki, w których istnieje więcej niż jeden wiersz lub nie wierszy zwracanych.

AKTUALIZACJA : Kilka lat później mam kilka sztuczek, którymi mogę się podzielić. JdbcTemplatedziała znakomicie z lambdami java 8, dla których są przeznaczone poniższe przykłady, ale całkiem łatwo można użyć klasy statycznej, aby osiągnąć to samo.

Chociaż pytanie dotyczy typów prostych, te przykłady służą jako przewodnik w typowym przypadku wyodrębniania obiektów domeny.

Po pierwsze. Załóżmy, że dla uproszczenia masz obiekt konta z dwiema właściwościami Account(Long id, String name). Prawdopodobnie chciałbyś mieć RowMapperdla tego obiektu domeny.

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

Możesz teraz używać tego mapowania bezpośrednio w metodzie do mapowania Accountobiektów domeny z zapytania ( jtjest to JdbcTemplateinstancja).

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

Świetnie, ale teraz chcemy uzyskać nasz pierwotny problem i używamy mojego oryginalnego rozwiązania, ponownie wykorzystując RowMapperdo wykonania mapowania za nas.

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

Świetnie, ale jest to wzór, który możesz i zechcesz powtórzyć. Możesz więc utworzyć ogólną metodę fabryki, aby utworzyć nową ResultSetExtractordla zadania.

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

Tworzenie ResultSetExtractorteraz staje się trywialne.

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

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

Mam nadzieję, że pomoże to pokazać, że można teraz dość łatwo łączyć części w potężny sposób, aby uprościć domenę.

AKTUALIZACJA 2 : Połącz z opcjonalnym dla wartości opcjonalnych zamiast null.

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

Który teraz, gdy zostanie użyty, może mieć następujące cechy:

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

To nie jest dobre rozwiązanie, ponieważ w przepływie kontroli polegasz na wyjątkach. W twoim rozwiązaniu normalne jest otrzymywanie wyjątków, normalne jest umieszczanie ich w dzienniku.

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

moje rozwiązanie może nie jest najbardziej eleganckie, ale przynajmniej moje. Podajesz przykład queryForObjectList, który nie jest nawet opcją w Jdbctemplate.
Byron

1
Jedyną wadą jest to, że gdyby typ zwracany był typem złożonym, budowałbyś wiele obiektów i tworzyłby instancję listy, również ResultSet.next()byłby wywoływany niepotrzebnie. ResultSetExtractorW tym przypadku użycie a jest znacznie wydajniejszym narzędziem.
Brett Ryan

a co, jeśli brak wartości jest opcją, ale nie ma więcej niż jednej? Często mam ten wzorzec i chciałbym mieć do tego celu obiekt queryForOptionalObject na wiosnę.
Guillaume,

7

Ok, wymyśliłem to. Po prostu zapakowałem go w próbny chwyt i odesłałem zero.

    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
Nie rozumiem, dlaczego jest to takie złe i dlaczego otrzymałeś za to tak wiele głosów przeciw, z wyjątkiem bycia fundamentalistą zgodnie z zasadą „nie ma przepływu programów w wyjątkach”. Po prostu zastąpiłbym ślad stosu wydruku komentarzem wyjaśniającym sprawę i nie robiłbym nic więcej.
Guillaume,

7

Właściwie możesz grać JdbcTemplatei dostosowywać własną metodę, jak chcesz. Proponuję zrobić coś takiego:

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

Działa jak oryginał jdbc.queryForObject, ale bezthrow new EmptyResultDataAccessException czasu size == 0.


@Abdull UserMapper implements RowMapper<String>.
Brett Ryan

Myślę, że jest to najlepsza odpowiedź, ponieważ daje najkrótszą składnię
Stan Sokolov

DataAccessUtils.singleResult(...)jest tym, czego szukałem. Dzięki
Drakes

7

Ponieważ zwracanie wartości null, gdy nie ma danych, jest czymś, co chcę robić często podczas korzystania z queryForObject, uważam, że przydatne jest rozszerzenie JdbcTemplate i dodanie metody queryForNullableObject podobnej do poniższej.

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

}

Możesz teraz użyć tego w swoim kodzie w taki sam sposób, w jaki użyłeś queryForObject

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

Chciałbym wiedzieć, czy ktoś uważa, że ​​to dobry pomysł?


1
Jest i powinno być wiosną
Guillaume,

4

Używając Java 8 lub nowszej, możesz używać Optionalstrumieni i Java.

Możesz więc po prostu użyć JdbcTemplate.queryForList()metody, utworzyć Stream i użyć, Stream.findFirst()który zwróci pierwszą wartość Stream lub pusty 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();
}

Aby poprawić wydajność zapytania, możesz dołączyć LIMIT 1do zapytania, aby nie więcej niż 1 pozycja była przesyłana z bazy danych.


1
Ładnie i czysto. Brak dodatkowych ifów lub lambd. Lubię to.
BeshEater

2

Możesz użyć funkcji grupy, aby zapytanie zawsze zwracało wynik. to znaczy

MIN(ID_NMB_SRZ)

1

W Postgres możesz sprawić, że prawie każde zapytanie o pojedynczej wartości zwróci wartość lub wartość null, opakowując je:

SELECT (SELECT <query>) AS value

i dzięki temu unikaj złożoności rozmówcy.


1

Ponieważ getJdbcTemplate (). QueryForMap oczekuje minimalnego rozmiaru wynoszącego jeden, ale kiedy zwraca wartość null, pokazuje EmptyResultDataAccesso fix dis, kiedy można użyć poniższej logiki

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

Zajmowałem się tym już wcześniej i pisałem na wiosennych forach.

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

Otrzymaliśmy radę, aby użyć typu SQlQuery. Oto przykład tego, co zrobiliśmy, próbując uzyskać wartość z bazy danych, której może tam nie być.

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

W DAO po prostu dzwonimy ...

Long id = findID.findObject(id);

Nie ma jasności co do wydajności, ale działa i jest schludny.


0

W przypadku Byrona możesz spróbować tego ...

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

robić

    jdbcTemplate.queryForList(sql, String.class)

pracy, upewnij się, że Twój jdbcTemplate jest typu

    org.springframework.jdbc.core.JdbcTemplate

0

Możemy użyć zapytania zamiast queryForObject, główna różnica między zapytanie a queryForObject polega na tym, że lista zwracanych zapytań Object (oparta na typie zwracanym przez mapowanie wierszy) i ta lista może być pusta, jeśli żadne dane nie są odbierane z bazy danych, podczas gdy queryForObject zawsze oczekuje, że tylko jeden obiekt będzie pobrane z db ani null, ani wiele wierszy, aw przypadku, gdy wynik jest pusty, queryForObject rzuca EmptyResultDataAccessException, napisałem jeden kod za pomocą zapytania, które rozwiąże problem EmptyResultDataAccessException w przypadku wyniku zerowego.

----------


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

Zwracanie a przez IMHO nulljest złym rozwiązaniem, ponieważ teraz masz problem z wysłaniem i zinterpretowaniem go na (prawdopodobnie) kliencie front-end. Miałem ten sam błąd i rozwiązałem go, po prostu zwracając plik List<FooObject>. Użyłem JDBCTemplate.query().

Na początku (klient sieciowy Angular) po prostu sprawdzam listę i jeśli jest pusta (ma zerową długość), traktuję ją jako brak rekordów.


-1

Po prostu łapię ten „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;
    }
}

wtedy możesz sprawdzić:

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

Możemy użyć zapytania zamiast queryForObject
ABHAY JOHRI

1
Nadużywanie takich wyjątków jest zwykle uważane za złą praktykę. Wyjątki nie dotyczą przewidywalnego przepływu logiki programu, ale dotyczą wyjątkowych sytuacji.
Chris Baker
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.