Kueri template jdbct untuk string: EmptyResultDataAccessException: Ukuran hasil salah: diharapkan 1, aktual 0

105

Saya menggunakan Jdbctemplate untuk mengambil nilai String tunggal dari db. Inilah metode saya.

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

Dalam skenario saya, sangat mungkin untuk TIDAK mendapatkan hasil pada kueri saya, jadi pertanyaan saya adalah bagaimana cara mengatasi pesan kesalahan berikut.

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

Tampaknya bagi saya bahwa saya harus mendapatkan kembali nol daripada membuat pengecualian. Bagaimana cara memperbaikinya? Terima kasih sebelumnya.

Byron
sumber

Jawaban:

179

Dalam JdbcTemplate, queryForInt, queryForLong, queryForObjectsemua metode mengharapkan seperti yang dijalankan permintaan akan kembali satu dan hanya satu baris. Jika Anda tidak mendapatkan baris atau lebih dari satu baris yang akan menghasilkan IncorrectResultSizeDataAccessException. Sekarang cara yang benar adalah tidak menangkap pengecualian ini atau EmptyResultDataAccessException, tetapi pastikan kueri yang Anda gunakan harus mengembalikan hanya satu baris. Jika sama sekali tidak memungkinkan, gunakan querymetode sebagai gantinya.

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
sumber
Seperti disebutkan di bawah, satu-satunya kelemahan di sini adalah jika tipe yang dikembalikan adalah tipe kompleks, Anda akan membuat beberapa objek dan membuat instance daftar, juga ResultSet.next()akan dipanggil jika tidak perlu. Dalam kasus ini, menggunakan alat ResultSetExtractoryang jauh lebih efisien.
Brett Ryan
3
Ada tanda kurung yang hilang dalam definisi kelas anonim - RowMapper baru ()
Janis Koluzs
Saya dengan Brett dalam hal ini. ResultSetExtractor lebih bersih :)
laher
2
Hai @Rakesh, kenapa tidak hanya return nullmasuk catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia
1
Hei! Bisakah saya bertanya mengapa 'Sekarang cara yang benar adalah tidak menangkap pengecualian ini', mengingat jika Anda menggunakan queryForObject? Apa yang salah dengan menangkap pengecualian dalam kasus queryForObject? Terima kasih :)
Michael Stokes
48

Anda juga dapat menggunakan a, ResultSetExtractorbukan RowMapper. Keduanya semudah satu sama lain, satu-satunya perbedaan adalah Anda menelepon 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;
        }
    });
}

The ResultSetExtractormemiliki manfaat tambahan bahwa Anda dapat menangani semua kasus di mana ada lebih dari satu baris atau tidak ada baris yang dikembalikan.

PEMBARUAN : Beberapa tahun berlalu dan saya memiliki beberapa trik untuk dibagikan. JdbcTemplatebekerja luar biasa dengan java 8 lambda yang dirancang untuk contoh berikut tetapi Anda dapat dengan mudah menggunakan kelas statis untuk mencapai hal yang sama.

Sementara pertanyaannya adalah tentang tipe sederhana, contoh ini berfungsi sebagai panduan untuk kasus umum penggalian objek domain.

Pertama. Misalkan Anda memiliki objek akun dengan dua properti untuk kesederhanaan Account(Long id, String name). Anda mungkin ingin memiliki RowMapperuntuk objek domain ini.

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

Sekarang Anda dapat menggunakan mapper ini secara langsung di dalam metode untuk memetakan Accountobjek domain dari kueri ( jtadalah sebuah JdbcTemplateinstance).

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

Bagus, tapi sekarang kami menginginkan masalah asli kami dan kami menggunakan solusi asli saya menggunakan kembali RowMapperuntuk melakukan pemetaan untuk kami.

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

Bagus, tetapi ini adalah pola yang mungkin dan ingin Anda ulangi. Jadi, Anda dapat membuat metode pabrik umum untuk membuat yang baru ResultSetExtractoruntuk tugas tersebut.

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

Membuat ResultSetExtractorsekarang menjadi hal yang sepele.

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

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

Saya harap ini membantu untuk menunjukkan bahwa Anda sekarang dapat dengan mudah menggabungkan bagian-bagian dengan cara yang ampuh untuk membuat domain Anda lebih sederhana.

UPDATE 2 : Gabungkan dengan Opsional untuk nilai opsional, bukan null.

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

Yang sekarang bila digunakan bisa memiliki yang berikut:

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

Itu bukan solusi yang baik karena Anda mengandalkan pengecualian untuk aliran kontrol. Dalam solusi Anda, wajar untuk mendapatkan pengecualian, itu normal untuk memilikinya di log.

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
sumber
solusi saya mungkin bukan yang paling elegan tetapi setidaknya solusi saya. Anda memberi contoh queryForObjectList yang bahkan bukan merupakan opsi dengan Jdbctemplate.
Byron
1
Satu-satunya kelemahan di sini adalah jika tipe yang dikembalikan adalah tipe kompleks, Anda akan membuat beberapa objek dan membuat instance daftar, juga ResultSet.next()akan dipanggil jika tidak perlu. Dalam kasus ini, menggunakan alat ResultSetExtractoryang jauh lebih efisien.
Brett Ryan
dan bagaimana jika tidak memiliki nilai adalah suatu pilihan, tetapi tidak memiliki lebih dari satu? Saya sering memiliki pola ini, dan saya ingin memiliki queryForOptionalObject di Spring untuk tujuan ini.
Guillaume
7

Oke, saya sudah menemukannya. Saya baru saja membungkusnya dengan mencoba menangkap dan mengirim kembali nol.

    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
sumber
1
Saya tidak mengerti mengapa hal ini begitu buruk dan mengapa Anda menerima begitu banyak suara negatif untuk itu, selain sebagai fundamentalis pada prinsip 'tidak ada aliran program dalam pengecualian'. Saya hanya akan mengganti jejak printstack dengan komentar yang menjelaskan kasus ini dan tidak melakukan apa pun.
Guillaume
7

Sebenarnya, Anda dapat bermain JdbcTemplatedan menyesuaikan metode Anda sendiri sesuka Anda. Saran saya adalah membuat sesuatu seperti ini:

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

Ini berfungsi seperti aslinya jdbc.queryForObject, tetapi tanpa throw new EmptyResultDataAccessExceptionkapan size == 0.

Alex
sumber
@Abdull UserMapper implements RowMapper<String>.
Brett Ryan
Saya pikir ini adalah jawaban terbaik di sini karena memberikan sintaks terpendek
Stan Sokolov
DataAccessUtils.singleResult(...)adalah apa yang saya cari. Thx
Drakes
7

Sejak mengembalikan null ketika tidak ada data adalah sesuatu yang sering saya lakukan saat menggunakan queryForObject, saya merasa berguna untuk memperluas JdbcTemplate dan menambahkan metode queryForNullableObject yang mirip dengan di bawah ini.

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

}

Anda sekarang dapat menggunakan ini di kode Anda dengan cara yang sama seperti Anda menggunakan queryForObject

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

Saya akan tertarik untuk mengetahui apakah ada orang lain yang menganggap ini ide yang bagus?

Stewart Evans
sumber
1
Memang, dan seharusnya di Musim Semi
Guillaume
4

Menggunakan Java 8 atau yang lebih baru, Anda dapat menggunakan Optionaldan Java Streams.

Jadi Anda cukup menggunakan JdbcTemplate.queryForList()metode ini, membuat Stream dan menggunakan Stream.findFirst()yang akan mengembalikan nilai pertama dari Stream atau kosong 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();
}

Untuk meningkatkan kinerja kueri, Anda bisa menambahkan LIMIT 1ke kueri Anda, jadi tidak lebih dari 1 item yang ditransfer dari database.

Samuel Philipp
sumber
1
Bagus dan bersih. Tidak ada tambahan ifs atau lambda. Saya suka itu.
BeshEater
2

Anda bisa menggunakan fungsi grup sehingga kueri Anda selalu mengembalikan hasil. yaitu

MIN(ID_NMB_SRZ)
DS.
sumber
1

Di Postgres, Anda dapat membuat hampir semua kueri nilai mengembalikan nilai atau null dengan membungkusnya:

SELECT (SELECT <query>) AS value

dan karenanya hindari kerumitan pada pemanggil.

Kaya
sumber
1

Karena getJdbcTemplate (). QueryForMap mengharapkan ukuran minimum satu tetapi ketika mengembalikan null, itu menunjukkan EmptyResultDataAccesso perbaiki dis ketika dapat menggunakan logika di bawah ini

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

Saya menangani ini sebelumnya & telah diposting di forum musim semi.

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

Saran yang kami terima adalah menggunakan jenis SQlQuery. Berikut adalah contoh dari apa yang kami lakukan saat mencoba mendapatkan nilai dari DB yang mungkin tidak ada.

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

Di DAO lalu kita sebut saja ...

Long id = findID.findObject(id);

Performanya tidak jelas, tetapi berfungsi dan rapi.

grbonk.dll
sumber
0

Untuk Byron, Anda bisa mencoba ini ..

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

untuk membuat

    jdbcTemplate.queryForList(sql, String.class)

berfungsi, pastikan jdbcTemplate Anda bertipe

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

Kita bisa menggunakan query daripada queryForObject, perbedaan utama antara query dan queryForObject adalah query yang mengembalikan daftar Object (berdasarkan tipe pengembalian Row mapper) dan daftar tersebut bisa kosong jika tidak ada data yang diterima dari database sementara queryForObject selalu mengharapkan hanya satu objek diambil dari db bukan nol atau beberapa baris dan jika hasilnya kosong maka queryForObject melempar EmptyResultDataAccessException, saya telah menulis satu kode menggunakan kueri yang akan mengatasi masalah EmptyResultDataAccessException dalam kasus hasil null.

----------


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

IMHO mengembalikan a nulladalah solusi yang buruk karena sekarang Anda memiliki masalah dalam mengirim dan menafsirkannya di klien ujung depan (kemungkinan). Saya mengalami kesalahan yang sama dan saya mengatasinya hanya dengan mengembalikan file List<FooObject>. Saya dulu JDBCTemplate.query().

Di ujung depan (klien web Angular), saya cukup memeriksa daftar dan jika kosong (panjang nol), perlakukan sebagai tidak ada catatan yang ditemukan.

sepertijudo
sumber
-1

Saya baru saja menangkap "EmptyResultDataAccessException" ini

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

lalu Anda dapat memeriksa:

if(m == null){
    // insert operation.
}else{
    // update operation.
}
Eddy
sumber
Kita bisa menggunakan query daripada queryForObject
ABHAY JOHRI
1
Biasanya dianggap praktik buruk hingga menyalahgunakan pengecualian seperti ini. Pengecualian bukan untuk alur logika program yang dapat diprediksi, melainkan untuk situasi luar biasa.
Chris Baker