[java] 문자열에 대한 Jdbctemplate 쿼리 : EmptyResultDataAccessException : 잘못된 결과 크기 : 예상 1, 실제 0

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

예외를 던지는 대신 null을 반환해야 할 것 같습니다. 이 문제를 어떻게 해결할 수 있습니까? 미리 감사드립니다.



답변

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


답변

당신은 또한 사용할 수 있습니다 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다음 예제가 설계된 Java 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쿼리에서 도메인 개체를 ( jtA는 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 : null 대신 선택적 값에 대해 Optional 과 결합하십시오 .

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


답변

제어 흐름에 대한 예외에 의존하고 있기 때문에 좋은 솔루션이 아닙니다. 솔루션에서 예외가 발생하는 것은 정상이며 로그에 예외가있는 것은 정상입니다.

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


답변

실제로 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.


답변

데이터가 없을 때 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);

다른 사람이 이것이 좋은 생각이라고 생각하는지 알고 싶습니다.


답변

좋아, 알아 냈어. 나는 그것을 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;
    }


답변

Java 8 이상을 사용하면 Optional및 Java Streams를 사용할 수 있습니다 .

따라서 JdbcTemplate.queryForList()메서드를 사용하고 Stream을 만들고 Stream 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있으므로 데이터베이스에서 항목이 하나 이상 전송되지 않습니다.