2012-05-15 24 views
75

我正在使用Jdbctemplate從數據庫檢索單個字符串值。這是我的方法。Jdbctemplate查詢字符串:EmptyResultDataAccessException:不正確的結果大小:預計1,實際0

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 

在我看來,我應該找回空值而不是拋出異常。我怎樣才能解決這個問題?提前致謝。

回答

129

在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.  
} 
+0

謝謝@Rakesh,這個解決方案工作。更乾淨。 – Byron

+0

如下所述,這裏唯一的缺點是,如果返回類型是一個複雜類型,那麼您將構建多個對象並實例化一個列表,並且'ResultSet.next()'將被不必要地調用。在這種情況下使用'ResultSetExtractor'是一個更加高效的工具。 –

+2

匿名類定義中缺少括號 - 新的RowMapper **()** –

3

好吧,我明白了。我只是將它包裝在try catch中併發回null。

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

我不明白爲什麼會這樣不好,爲什麼會收到這麼多downvote它,APPART您作爲原則「內無例外程序流」原教旨主義者。我只是用解釋案例的評論來替換printstack trace,而不採取其他措施。 – Guillaume

16

這不是一個好的解決方案,因爲您依賴於控制流的異常。在你的解決方案中,獲取異常是正常的,在日誌中使用它們是正常的。

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

queryForObjectList不是Springframework中的一個選項。 – Byron

+0

我的解決方案可能不是最優雅的,但至少是礦山工程。你給出了一個queryForObjectList的例子,它甚至不是Jdbctemplate的選項。 – Byron

+1

對不起,它是#queryForList –

2

你可以使用一組功能,使您的查詢總是返回一個結果。 即

MIN(ID_NMB_SRZ) 
21

你也可以使用一個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有可以處理那裏有多個行或沒有行返回的所有情況下的額外好處。

UPDATE:幾年後,我有幾個技巧可以分享。 JdbcTemplate與java 8 lambda類似地工作,下面的例子是爲你設計的,但你可以很容易地使用靜態類來實現。

雖然問題是關於簡單類型,但這些示例可用作提取域對象的常見情況的指導。

首先。假設您有一個賬戶對象,其中包含兩個屬性以簡化操作Account(Long id, String name)。您可能希望爲此域對象設置RowMapper

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

現在可以直接使用,方法中的這個映射器Account域對象從查詢(jtJdbcTemplate實例)進行映射。

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 = 
     nullableExtractor(MAPPER_ACCOUNT); 

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

我希望這有助於證明你現在可以很容易地在一個強大的方式來讓您的站點更簡單的結合部位。

UPDATE 2:與Optional組合爲可選值而不是null。

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 = 
     nullableOptionalExtractor(MAPPER_DISCOUNT); 

public double getDiscount(long accountId) { 
    return jt.query(SELECT_DISCOUNT, EXTRACTOR_DISCOUNT, accountId) 
      .orElse(0.0); 
} 
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 EmptyResultDataAccessExceptionsize == 0

+0

UserMapper()'? – Abdull

+0

@Abudull UserMapper實現了RowMapper '。 –

+0

我認爲這是最好的答案,因爲它給出了最短的語法 –

7

由於在沒有數據時返回null是我在使用queryForObject時經常要做的事情,我發現擴展JdbcTemplate並添加類似於下面的queryForNullableObject方法很有用。你用

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

它是,它應該在Spring – Guillaume

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

由於getJdbcTemplate()。queryForMap預計一個的最小尺寸,但是當它返回null它顯示EmptyResultDataAccesso修復DIS時,可以使用下面的邏輯

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

在Postgres裏,你可以做幾乎任何單個值查詢通過包裝它返回一個值或空:

SELECT (SELECT <query>) AS value 

因此避免了調用者的複雜性。

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

我正好趕上這個「 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. 
} 
+0

我們可以使用query而不是queryForObject –

0

我們可以使用查詢,而不是queryForObject,查詢和queryForObject之間的主要區別在於對象的該查詢返回列表(基於行映射器返回類型)和列表可以如果沒有從數據庫接收到數據,則爲空,而queryForObject始終只希望從db中獲取單個對象,既不爲空也不爲多行,如果結果爲空,則queryForObject拋出EmptyResultDataAccessException,我寫了一個使用查詢的代碼來解決如果結果爲空,則返回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); 
     } 
    } 
相關問題