문자열에 대한 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

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

답변:


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이 경우 a를 사용하는 것이 훨씬 더 효율적인 도구입니다.
Brett Ryan

3
새로운 RowMapper의 - 익명 클래스 정의에서 누락 된 괄호있다 ()
제니스 Koluzs

나는 이것에 대해 브렛과 함께 있습니다. ResultSetExtractor 청소기입니다 :)
laher

2
안녕하세요 @Rakesh, 왜뿐만 아니라 return null에서 catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia 2014-06-18

1
야! 왜 queryForObject를 사용하고 있는지 고려할 때 '이제 올바른 방법은이 예외를 포착하지 않는 것'인지 물어볼 수 있습니까? 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다음 예제가 설계된 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);
}

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

내 솔루션은 가장 우아하지는 않지만 적어도 내 작품입니다. Jdbctemplate의 옵션도 아닌 queryForObjectList의 예를 제공합니다.
Byron

1
여기서 유일한 단점은 반환 유형이 복합 유형 인 경우 여러 개체를 작성하고 목록을 인스턴스화하고 ResultSet.next()불필요하게 호출된다는 것입니다. ResultSetExtractor이 경우 a를 사용하는 것이 훨씬 더 효율적인 도구입니다.
Brett Ryan

가치가없는 것이 하나의 옵션이지만 둘 이상이없는 경우에는 어떻게합니까? 나는이 패턴을 자주 가지고 있으며이 목적을 위해 Spring에 queryForOptionalObject를 갖고 싶습니다.
Guillaume

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>.
Brett Ryan

나는 그것이 가장 짧은 구문을 제공 여기에 가장 좋은 대답을 생각
스탠 소콜로프

DataAccessUtils.singleResult(...)내가 찾던 것입니다. Thx
Drakes

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
그것은이며, 봄에 있어야합니다
기욤

6

좋아, 알아 냈어. 나는 그것을 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
나는 이것이 왜 그렇게 나쁜지, 왜 당신이 그것에 대해 그렇게 많은 반대표를 받았는지 모르겠습니다. 나는 printstack 추적을 사건을 설명하는 주석으로 바꾸고 다른 것은 아무것도하지 않을 것입니다.
Guillaume

4

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있으므로 데이터베이스에서 항목이 하나 이상 전송되지 않습니다.


1
좋고 깨끗합니다. 추가 if 또는 람다가 없습니다. 나는 그것을 좋아한다.
BeshEater

2

쿼리가 항상 결과를 반환하도록 그룹 함수를 사용할 수 있습니다. 즉

MIN(ID_NMB_SRZ)

1

Postgres에서는 거의 모든 단일 값 쿼리를 래핑하여 값 또는 null을 반환하도록 만들 수 있습니다.

SELECT (SELECT <query>) AS value

따라서 호출자의 복잡성을 피하십시오.


1

getJdbcTemplate (). queryForMap은 최소 크기 1을 예상하지만 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;
}

0

나는 이것을 전에 다루었 고 봄 포럼에 게시했습니다.

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

우리가받은 조언은 SQlQuery 유형을 사용하라는 것입니다. 여기에 없을 수도있는 DB에서 값을 가져 오려고 할 때 수행 한 작업의 예가 있습니다.

@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

Byron의 경우 이것을 시도 할 수 있습니다 ..

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를 사용할 수 있습니다. query와 queryForObject의 주요 차이점은 쿼리가 Object의 반환 목록 (행 매퍼 반환 유형 기준)과 데이터베이스에서 데이터가 수신되지 않으면 해당 목록이 비어있을 수 있지만 queryForObject는 항상 단일 객체 만 db에서 null도 여러 행도 가져 오지 않았고 결과가 비어있는 경우 queryForObject가 EmptyResultDataAccessException을 throw하면 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가 a를 반환하는 것은 잘못된 솔루션입니다. 이제 (아마도) 프런트 엔드 클라이언트에서 전송하고 해석하는 데 문제가 있기 때문입니다. 나는 같은 오류가 있었고 단순히 List<FooObject>. 나는 JDBCTemplate.query().

프런트 엔드 (Angular 웹 클라이언트)에서 목록을 검사하고 비어있는 경우 (길이가 0 인 경우) 레코드를 찾을 수없는 것으로 처리합니다.


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

우리의 queryForObject 대신 쿼리를 사용할 수 있습니다
ABHAY JOHRI에게

1
일반적으로 이와 같은 예외를 남용하는 것은 나쁜 습관으로 간주됩니다. 예외는 예측 가능한 프로그램 논리 흐름이 아니라 예외적 인 상황을위한 것입니다.
크리스 베이커
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.