Dize için Jdbctemplate sorgusu: EmptyResultDataAccessException: Hatalı sonuç boyutu: 1 beklenen, gerçek 0


105

Db'den tek bir String değeri almak için Jdbctemplate kullanıyorum. İşte benim yöntemim.

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

Benim senaryomda, sorguma bir isabet ALMAMAK tamamen mümkün, bu yüzden sorum aşağıdaki hata mesajını nasıl aşacağım.

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

Bana öyle geliyor ki, bir istisna atmak yerine sadece bir boş değeri geri almalıyım. Bunu nasıl düzeltebilirim? Şimdiden teşekkürler.

Yanıtlar:


179

JdbcTemplate yılında queryForInt, queryForLong, queryForObjectsorgu idam tüm bu yöntemler beklediği bir ve yalnızca bir satır döndürür. Hiçbir satır veya birden fazla satır almazsanız, bu sonuçlanır IncorrectResultSizeDataAccessException. Şimdi doğru yol, bu istisnayı yakalamak değil EmptyResultDataAccessException, kullandığınız sorgunun yalnızca bir satır döndürmesi gerektiğinden emin olmaktır. Hiç mümkün değilse, onun queryyerine yöntemi kullanın.

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

Aşağıda belirtildiği gibi, buradaki tek dezavantaj, dönüş türü karmaşık bir tür olsaydı, birden çok nesne oluşturuyor ve bir listeyi başlatıyor olmanızın da ResultSet.next()gereksiz yere çağrılmasıdır. ResultSetExtractorBu durumda a kullanmak çok daha verimli bir araçtır.
Brett Ryan

3
Anonim sınıf tanımında eksik bir parantez var - new RowMapper ()
Janis Koluzs

Bu konuda Brett ile birlikteyim. ResultSetExtractor temizleyici :) olduğunu
laher

2
Merhaba @Rakesh, neden sadece return nulliçinde catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia

1
Hey! QueryForObject kullanıp kullanmadığınızı göz önünde bulundurarak neden 'Şimdi doğru yol bu istisnayı yakalamak değil' diye sorabilir miyim? QueryForObject durumunda bir istisna yakalamak yanlış olur? Teşekkürler :)
Michael Stokes

48

Ayrıca a ResultSetExtractoryerine a da kullanabilirsiniz RowMapper. İkisi de birbiri kadar kolaydır, tek fark sizin aramanızdır 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;
        }
    });
}

ResultSetExtractorBirden fazla satır var bütün davaları veya iade hiçbir satır işleyebileceği yararı vardır.

GÜNCELLEME : Birkaç yıl geçti ve paylaşacak birkaç numaram var. JdbcTemplateAşağıdaki örneklerin tasarlandığı java 8 lambdas ile mükemmel çalışır, ancak aynı şeyi elde etmek için statik bir sınıfı kolayca kullanabilirsiniz.

Soru basit türlerle ilgili olsa da, bu örnekler, etki alanı nesnelerini ayıklamanın yaygın durumu için bir kılavuz görevi görür.

İlki. Basit olması için iki özelliğe sahip bir hesap nesneniz olduğunu varsayalım Account(Long id, String name). RowMapperBu etki alanı nesnesi için büyük olasılıkla bir sahip olmak isteyeceksiniz .

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

Artık bu eşleştiriciyi Account, bir sorgudan ( jtbir JdbcTemplateörnektir) etki alanı nesnelerini eşlemek için doğrudan bir yöntem içinde kullanabilirsiniz .

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

Harika, ama şimdi asıl sorunumuzu istiyoruz RowMapperve haritalamayı bizim için gerçekleştirmek için orijinal çözümümü tekrar kullanarak kullanıyoruz .

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

Harika, ama bu tekrarlamak isteyebileceğiniz ve isteyeceğiniz bir kalıptır. Böylece ResultSetExtractor, görev için yeni bir tane oluşturmak üzere genel bir fabrika yöntemi oluşturabilirsiniz .

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

ResultSetExtractorŞimdi oluşturmak önemsiz hale gelir.

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

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

Umarım bu, etki alanınızı daha basit hale getirmek için parçaları güçlü bir şekilde kolayca birleştirebileceğinizi göstermeye yardımcı olur.

GÜNCELLEME 2 : Boş yerine isteğe bağlı değerler için İsteğe Bağlı ile birleştirin .

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

Şimdi kullanıldığında aşağıdakilere sahip olabilir:

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

Bu iyi bir çözüm değil çünkü kontrol akışı için istisnalara güveniyorsunuz. Çözümünüzde istisnalar almanız normaldir, bunların günlükte olması normaldir.

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

benim çözümüm en zarif olmayabilir ama en azından benim işlerim. Jdbctemplate ile bir seçenek bile olmayan bir queryForObjectList örneği veriyorsunuz.
Byron

1
Buradaki tek dezavantaj, dönüş türü karmaşık bir tür olsaydı, birden çok nesne oluşturuyor ve bir listeyi başlatıyor olmanın da ResultSet.next()gereksiz yere çağrılacağıdır. ResultSetExtractorBu durumda a kullanmak çok daha verimli bir araçtır.
Brett Ryan

ve ya hiç değerin olmaması bir seçenekse, ancak birden fazlasına sahip olmamak? Bu kalıba sık sık sahibim ve bu amaçla Spring'de bir queryForOptionalObject olmasını istiyorum.
Guillaume

7

Tamam, anladım. Ben sadece onu bir deneme yakalamaya sardım ve boş geri gönder.

    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
Bunun neden bu kadar kötü olduğunu ve bunun için neden bu kadar olumsuz oy aldığınızı anlamıyorum, 'istisna dahilinde program akışı yok' prensibinde köktendinci olduğunuz için apart. Baskı yığını izini olayı açıklayan bir yorumla değiştirip başka bir şey yapmazdım.
Guillaume

7

Aslında, JdbcTemplatekendi yönteminizle oynayabilir ve tercihinize göre özelleştirebilirsiniz. Benim önerim şöyle bir şey yapmak:

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

Bu orijinal olarak çalışır jdbc.queryForObject, ancak olmadan throw new EmptyResultDataAccessExceptionzaman size == 0.


@Abdull UserMapper implements RowMapper<String>.
Brett Ryan

Bence en kısa sözdizimini verdiği için buradaki en iyi cevap
Stan Sokolov

DataAccessUtils.singleResult(...)aradığım şeydi. Thx
Drakes

7

Veri olmadığında null döndürmek, queryForObject kullanırken sık sık yapmak istediğim bir şey olduğundan, JdbcTemplate'i genişletmeyi ve aşağıdakine benzer bir queryForNullableObject yöntemi eklemeyi yararlı buldum.

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

}

Artık bunu kodunuzda queryForObject ile aynı şekilde kullanabilirsiniz.

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

Başka birinin bunun iyi bir fikir olduğunu düşünmesi beni ilgilendirir mi?


1
Öyle ve ilkbaharda olmalı
Guillaume

4

Java 8 veya üstünü kullanarak bir Optionalve Java Akışlarını kullanabilirsiniz.

Böylece, JdbcTemplate.queryForList()yöntemi kullanabilir, bir Akış oluşturabilir ve Akışın Stream.findFirst()ilk değerini veya boş bir değeri döndürecek olanı kullanabilirsiniz 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();
}

Sorgunun performansını artırmak için sorgunuza ekleyebilirsiniz LIMIT 1, böylece veritabanından 1 öğeden fazlası aktarılmaz.


1
Güzel ve temiz. Ek ifs veya lambda yok. Bunu sevdim.
BeshEater

2

Sorgunuzun her zaman bir sonuç döndürmesi için bir grup işlevi kullanabilirsiniz. yani

MIN(ID_NMB_SRZ)

1

Postgres'te, hemen hemen her tek değer sorgusunu sararak bir değer veya boş değer döndürmesini sağlayabilirsiniz:

SELECT (SELECT <query>) AS value

ve böylece arayanın karmaşıklığını önleyin.


1

GetJdbcTemplate (). QueryForMap minimum bir boyut beklediğinden, ancak boş döndürdüğünde, aşağıdaki mantığı kullanabildiğinde EmptyResultDataAccesso düzeltme disini gösterir

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

Bununla daha önce ilgilenmiştim ve ilkbahar forumlarında yayınlamıştım.

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

Aldığımız tavsiye, bir tür SQlQuery kullanmaktı. İşte orada olmayabilecek bir DB'den bir değer elde etmeye çalışırken yaptığımız şeyin bir örneği.

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

DAO'da biz sadece ararız ...

Long id = findID.findObject(id);

Performans konusunda net değil, ama işe yarıyor ve düzenli.


0

Byron için bunu deneyebilirsiniz ..

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

yapmak

    jdbcTemplate.queryForList(sql, String.class)

iş, jdbcTemplate'inizin türünde olduğundan emin olun

    org.springframework.jdbc.core.JdbcTemplate

0

QueryForObject yerine sorgu kullanabiliriz, query ve queryForObject arasındaki en büyük fark, Object'in sorgu dönüş listesidir (Row mapper dönüş türüne göre) ve bu listenin veritabanından hiçbir veri alınmazsa boş olabilir, ancak queryForObject her zaman sadece tek bir nesnenin olmasını bekler. db'den ne null ne de birden çok satır getirildi ve sonucun boş olması durumunda queryForObject EmptyResultDataAccessException'ı attığında, boş sonuç durumunda EmptyResultDataAccessException sorununun üstesinden gelecek sorgu kullanarak bir kod yazdım.

----------


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

IMHO'nun a döndürmesi nullkötü bir çözüm çünkü artık (muhtemelen) ön uç istemcide gönderme ve yorumlama probleminiz var. Ben de aynı hatayı aldım ve basitçe bir List<FooObject>. Kullandım JDBCTemplate.query().

Ön uçta (Angular web istemcisi), listeyi basitçe inceliyorum ve eğer boşsa (sıfır uzunlukta), hiç kayıt bulunamadı gibi davranıyorum.


-1

Bu "EmptyResultDataAccessException" ı yakalıyorum

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

o zaman kontrol edebilirsiniz:

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

QueryForObject yerine sorgu kullanabiliriz
ABHAY JOHRI

1
Genellikle bunun gibi istisnaları kötüye kullanmak kötü bir uygulama olarak kabul edilir. İstisnalar tahmin edilebilir program mantık akışı için değildir, istisnai durumlar içindir.
Chris Baker
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.