स्ट्रिंग के लिए Jdbctemplate क्वेरी: EmptyResultDataAccessException: गलत परिणाम का आकार: अपेक्षित 1, वास्तविक 0


105

मैं db से एकल स्ट्रिंग मूल्य को पुनः प्राप्त करने के लिए Jdbctemplate का उपयोग कर रहा हूं। यहाँ मेरी विधि है।

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

मेरे परिदृश्य में यह संभव है कि मेरी क्वेरी पर कोई हिट न हो इसलिए मेरा प्रश्न है कि मुझे निम्नलिखित त्रुटि संदेश के बारे में कैसे पता चलेगा।

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

यह मुझे प्रतीत होगा कि मुझे केवल एक अपवाद को फेंकने के बजाय एक अशक्त वापस मिल जाना चाहिए। मैं इसे कैसे ठीक करूं? अग्रिम में धन्यवाद।

जवाबों:


179

JdbcTemplate, queryForIntमें queryForLong, queryForObjectऐसे सभी तरीके उम्मीद करते हैं कि निष्पादित क्वेरी एक और केवल एक पंक्ति लौटाएगी । यदि आपको कोई पंक्तियाँ या एक से अधिक पंक्तियाँ नहीं मिलती हैं, तो इसका परिणाम होगा IncorrectResultSizeDataAccessException। अब सही तरीका इस अपवाद को पकड़ने के लिए नहीं है या EmptyResultDataAccessException, लेकिन सुनिश्चित करें कि आप जिस क्वेरी का उपयोग कर रहे हैं उसे केवल एक पंक्ति में वापस करना चाहिए। यदि यह सब संभव नहीं है, तो 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.    
}

जैसा कि नीचे उल्लेख किया गया है, यहां एकमात्र दोष यह है कि यदि रिटर्न प्रकार एक जटिल प्रकार था, तो आप कई वस्तुओं का निर्माण कर रहे होंगे और किसी सूची को तत्काल बना रहे ResultSet.next()होंगे , यह भी अनावश्यक रूप से कहा जाएगा। ResultSetExtractorइस मामले में एक बहुत अधिक कुशल उपकरण का उपयोग करना है ।
ब्रेट रायन

3
अनाम कक्षा परिभाषा में एक लापता कोष्ठक है - नया RowMapper ()
Janis Koluzs

मैं इस पर ब्रेट के साथ हूं। ResultSetExtractor क्लीनर है :)
laher

2
हाय @Rakesh, क्यों न केवल return nullमें catch(EmptyResultDataAccessException exception){ return null; }?
विशाल जंज़ुकिया

1
अरे! क्या मैं पूछ सकता हूं कि 'अब सही तरीका इस अपवाद को पकड़ने के लिए नहीं है', यह देखते हुए कि क्या आप queryForObject का उपयोग कर रहे हैं? क्वेरीफॉरऑबजेक्ट के मामले में एक अपवाद को पकड़ने में क्या गलत होगा? धन्यवाद :)
माइकल स्टोक्स

48

तुम भी एक का उपयोग कर सकते ResultSetExtractorएक के बजाय RowMapper। दोनों एक दूसरे के समान आसान हैं, केवल एक अंतर है जिसे आप कहते हैं 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;
        }
    });
}

ResultSetExtractorअतिरिक्त लाभ यह है कि आप सभी मामलों में जहां एक से अधिक पंक्तियां देखते हैं या यदि कोई भी पंक्ति लौटे संभाल कर सकते हैं है।

अद्यतन : कई वर्षों से और मेरे पास साझा करने के लिए कुछ तरकीबें हैं। JdbcTemplateजावा 8 लैम्ब्डा के साथ शानदार तरीके से काम करता है, जिसके लिए निम्न उदाहरण तैयार किए गए हैं, लेकिन आप इसे प्राप्त करने के लिए एक स्थिर वर्ग का उपयोग आसानी से कर सकते हैं।

जबकि सवाल सरल प्रकारों के बारे में है, ये उदाहरण डोमेन ऑब्जेक्ट्स को निकालने के सामान्य मामले के लिए एक मार्गदर्शक के रूप में काम करते हैं।

सबसे पहले। मान लीजिए कि आपके पास सादगी के लिए दो गुणों वाली एक खाता वस्तु है Account(Long id, String name)। आप RowMapperइस डोमेन ऑब्जेक्ट के लिए चाहते हैं।

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

अब आप एक विधि के भीतर सीधे इस नक्शाकार उपयोग कर सकते हैं मैप करने के लिए Accountएक प्रश्न से डोमेन वस्तुओं ( jtएक है JdbcTemplateउदाहरण)।

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

महान, लेकिन अब हम अपनी मूल समस्या चाहते हैं और हम हमारे मूल समाधान का उपयोग RowMapperकरते हुए हमारे लिए मैपिंग का उपयोग करते हैं।

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

महान, लेकिन यह एक पैटर्न है जिसे आप दोहरा सकते हैं और दोहराना चाहेंगे। तो आप ResultSetExtractorकार्य के लिए एक नया बनाने के लिए एक सामान्य फैक्टरी विधि बना सकते हैं ।

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

ResultSetExtractorअब बनाना तुच्छ हो जाता है।

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

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

मुझे उम्मीद है कि यह यह दिखाने में मदद करता है कि अब आप अपने डोमेन को सरल बनाने के लिए शक्तिशाली तरीके से भागों को आसानी से जोड़ सकते हैं।

अद्यतन 2 : शून्य के बजाय वैकल्पिक मूल्यों के लिए एक वैकल्पिक के साथ गठबंधन ।

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

जो अब जब इस्तेमाल किया जा सकता है निम्नलिखित:

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

यह एक अच्छा समाधान नहीं है क्योंकि आप नियंत्रण प्रवाह के अपवादों पर भरोसा कर रहे हैं। आपके समाधान में अपवादों को प्राप्त करना सामान्य है, उन्हें लॉग में रखना सामान्य है।

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

मेरा समाधान सबसे सुरुचिपूर्ण नहीं है, लेकिन कम से कम मेरा काम करता है। आप queryForObjectList का उदाहरण देते हैं जो Jdbctemplate के साथ एक विकल्प भी नहीं है।
बायरन

1
यहाँ केवल दोष यह है कि यदि वापसी प्रकार एक जटिल प्रकार था तो आप कई वस्तुओं का निर्माण कर रहे होंगे और किसी सूची को तत्काल बना रहे ResultSet.next()होंगे , यह भी अनावश्यक रूप से कहा जाएगा। ResultSetExtractorइस मामले में एक बहुत अधिक कुशल उपकरण का उपयोग करना है ।
ब्रेट रायन

और क्या होगा अगर कोई मूल्य नहीं है एक विकल्प है, लेकिन एक से अधिक नहीं है? मेरे पास अक्सर यह पैटर्न होता है, और मैं इस उद्देश्य के लिए स्प्रिंग में एक क्वेरीफॉरऑपशनलऑब्जेक्ट रखना चाहूंगा।
ग्वेलियूम

7

वास्तव में, आप JdbcTemplateअपनी पसंद के अनुसार अपनी खुद की विधि के साथ खेल सकते हैं और अनुकूलित कर सकते हैं। मेरा सुझाव कुछ ऐसा बनाना है:

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

यह मूल के रूप में काम करता है jdbc.queryForObject, लेकिन बिना throw new EmptyResultDataAccessExceptionकब size == 0


@Abdull UserMapper implements RowMapper<String>
ब्रेट रायन

मुझे लगता है कि यह यहां सबसे अच्छा जवाब है क्योंकि यह सबसे छोटा वाक्यविन्यास देता है
स्टेन सोकोलोव

DataAccessUtils.singleResult(...)जिसकी मुझे तलाश थी वह है। Thx
Drakes

7

चूंकि डेटा नहीं होने पर अशक्त होने के बाद, कुछ ऐसा है जिसे मैं अक्सर करना चाहता हूं जब queryForObject का उपयोग करते हुए मैंने JdbcTemplate का विस्तार करना और नीचे के समान एक क्वेरीफ़्लोरबेलऑब्जेक्ट विधि जोड़ना उपयोगी पाया है।

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

}

अब आप इसे अपने कोड में उसी तरह से उपयोग कर सकते हैं जिस तरह से आपने queryForObject का उपयोग किया था

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

मुझे यह जानने में दिलचस्पी होगी कि क्या कोई और सोचता है कि यह एक अच्छा विचार है?


1
यह है, और यह
गिलोय

6

ठीक है, मैंने पता लगा लिया। मैंने इसे एक कोशिश में पकड़ा और वापस शून्य भेज दिया।

    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
मैं यह नहीं देखता कि यह इतना बुरा क्यों है और आपको इसके लिए इतनी गिरावट क्यों मिली, 'अपवाद के भीतर कोई कार्यक्रम प्रवाह' सिद्धांत पर एक कट्टरपंथी होने के लिए नहीं। मैं सिर्फ प्रिंटस्टैक ट्रेस को एक टिप्पणी के साथ बदल देता था जो मामले को समझाता था और कुछ नहीं करता था।
गिलाउल

4

जावा 8 या इसके बाद के संस्करण का उपयोग करके आप Optionalऔर जावा स्ट्रीम का उपयोग कर सकते हैं ।

तो आप बस JdbcTemplate.queryForList()विधि का उपयोग कर सकते हैं , एक स्ट्रीम बना सकते हैं और उपयोग Stream.findFirst()कर सकते हैं जो स्ट्रीम का पहला मान या एक खाली लौटाएगा 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();
}

क्वेरी के प्रदर्शन को बेहतर बनाने के लिए आप LIMIT 1अपनी क्वेरी को जोड़ सकते हैं , इसलिए डेटाबेस से 1 आइटम से अधिक स्थानांतरित नहीं किया जाता है।


1
अच्छा और साफ। कोई अतिरिक्त इफ्स या लैम्ब्डा नहीं। मुझें यह पसंद है।
बैशएटर

2

आप एक समूह फ़ंक्शन का उपयोग कर सकते हैं ताकि आपकी क्वेरी हमेशा परिणाम दे। अर्थात

MIN(ID_NMB_SRZ)

1

Postgres में, आप लगभग किसी भी एकल मान क्वेरी को मान या नल को वापस लाकर बना सकते हैं:

SELECT (SELECT <query>) AS value

और इसलिए फोन करने वाले में जटिलता से बचें।


1

चूंकि getJdbcTemplate ()। QueryForMap एक के न्यूनतम आकार की उम्मीद करता है, लेकिन जब यह शून्य हो जाता है तो यह दिखाता है कि EmptyResultDataAccesso डिस को ठीक करता है जब तर्क के नीचे उपयोग कर सकते हैं

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

मैंने इससे पहले वसंत मंचों में पोस्ट किया था।

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

हमें जो सलाह मिली वह SQlQuery के एक प्रकार का उपयोग करने के लिए थी। यहाँ एक उदाहरण है कि हमने एक डीबी से एक मूल्य प्राप्त करने की कोशिश की थी जो वहां नहीं था।

@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 में तो हम सिर्फ कॉल ...

Long id = findID.findObject(id);

प्रदर्शन पर स्पष्ट नहीं है, लेकिन यह काम करता है और साफ-सुथरा है।


0

बायरन के लिए, आप यह कोशिश कर सकते हैं ।।

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

बनाना

    jdbcTemplate.queryForList(sql, String.class)

काम करें, सुनिश्चित करें कि आपका jdbcTemplate प्रकार है

    org.springframework.jdbc.core.JdbcTemplate

0

हम queryForObject के बजाय क्वेरी का उपयोग कर सकते हैं, query और queryForObject के बीच प्रमुख अंतर यह है कि क्वेरी रिटर्न लिस्ट ऑफ़ ऑब्जेक्ट (रो मैपर रिटर्न प्रकार के आधार पर) और वह सूची रिक्त हो सकती है यदि कोई डेटा डेटाबेस से प्राप्त नहीं होता है, जबकि क्वेरी नॉरऑर्जेक्ट विशेष रूप से केवल एकल ऑब्जेक्ट की अपेक्षा करता है db से लिया गया न तो null और न ही कई पंक्तियाँ और यदि परिणाम रिक्त है, तो queryForObject फेंकता है EmptyResultDataAccessException, मैंने क्वेरी का उपयोग करते हुए एक कोड लिखा था जो null परिणाम के मामले में EmptyResultDataAccessException की समस्या को दूर करेगा।

----------


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 लौटना एक nullबुरा समाधान है क्योंकि अब आपको इसे (संभावित) फ्रंट एंड क्लाइंट पर भेजने और व्याख्या करने की समस्या है। मेरे पास एक ही त्रुटि थी और मैंने इसे केवल वापस करके हल किया List<FooObject>। मैंने इस्तेमाल किया JDBCTemplate.query()

सामने के छोर पर (कोणीय वेब क्लाइंट), मैं बस सूची की जांच करता हूं और अगर यह खाली (शून्य लंबाई) है, तो इसे रिकॉर्ड किए गए रिकॉर्ड के रूप में मानें।


-1

मैं बस इसे "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;
    }
}

तो आप देख सकते हैं:

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

हम क्वेरी का उपयोग कर सकते हैं बजाय
क्वेरीफॉरबिज के

1
आमतौर पर इस तरह के अपवादों का दुरुपयोग करने के लिए बुरा व्यवहार माना जाता है। अपवाद पूर्वानुमान कार्यक्रम तर्क प्रवाह के लिए नहीं हैं, वे असाधारण स्थितियों के लिए हैं।
क्रिस बेकर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.