Truy vấn jdbctemplate cho chuỗi: EmptyResultDataAccessException: Kích thước kết quả không chính xác: mong đợi 1, thực tế 0


105

Tôi đang sử dụng Jdbctemplate để truy xuất một giá trị Chuỗi đơn lẻ từ db. Đây là phương pháp của tôi.

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

Trong tình huống của tôi, hoàn toàn có thể KHÔNG nhận được truy vấn của tôi, vì vậy câu hỏi của tôi là làm cách nào để khắc phục thông báo lỗi sau.

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

Đối với tôi, dường như tôi chỉ nên lấy lại null thay vì ném một ngoại lệ. Làm thế nào tôi có thể sửa lỗi này? Cảm ơn trước.

Câu trả lời:


179

Trong JdbcTemplate, queryForInt, queryForLong, queryForObjecttất cả các phương pháp dự đoán như vậy mà thực hiện truy vấn sẽ trả một và chỉ một dòng. Nếu bạn không nhận được hàng hoặc nhiều hơn một hàng sẽ dẫn đến IncorrectResultSizeDataAccessException. Bây giờ, cách chính xác là không bắt ngoại lệ này hoặc EmptyResultDataAccessException, nhưng hãy đảm bảo rằng truy vấn bạn đang sử dụng chỉ trả về một hàng. Nếu không thể, hãy sử dụng queryphương pháp thay thế.

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

Như đã đề cập bên dưới, hạn chế duy nhất ở đây là nếu kiểu trả về là một kiểu phức tạp, bạn sẽ xây dựng nhiều đối tượng và khởi tạo một danh sách, cũng ResultSet.next()sẽ được gọi là không cần thiết. Sử dụng a ResultSetExtractorlà một công cụ hiệu quả hơn nhiều trong trường hợp này.
Brett Ryan

3
Có một dấu ngoặc mất tích trong định nghĩa lớp nặc danh - RowMapper mới ()
Janis Koluzs

Tôi với Brett về việc này. ResultSetExtractor sạch hơn :)
laher

2
Xin chào @Rakesh, tại sao không chỉ return nulltrong catch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia

1
Chào! Tôi có thể hỏi tại sao 'Bây giờ cách chính xác là không bắt ngoại lệ này', xem xét liệu bạn có đang sử dụng queryForObject không? Điều gì sẽ sai khi bắt một ngoại lệ trong trường hợp queryForObject? Cảm ơn :)
Michael Stokes

48

Bạn cũng có thể sử dụng a ResultSetExtractorthay vì a RowMapper. Cả hai đều dễ dàng như nhau, sự khác biệt duy nhất là bạn gọi 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;
        }
    });
}

Các ResultSetExtractorcó thêm lợi ích mà bạn có thể xử lý tất cả trường hợp có nhiều hơn một hàng hoặc không có hàng trả lại.

CẬP NHẬT : Vài năm trôi qua và tôi có một vài thủ thuật để chia sẻ. JdbcTemplatehoạt động tuyệt vời với java 8 lambdas mà các ví dụ sau đây được thiết kế cho nhưng bạn có thể khá dễ dàng sử dụng một lớp tĩnh để đạt được điều tương tự.

Mặc dù câu hỏi là về các kiểu đơn giản, nhưng những ví dụ này đóng vai trò là hướng dẫn cho trường hợp phổ biến là trích xuất các đối tượng miền.

Trước hết. Hãy giả sử rằng bạn có một đối tượng tài khoản với hai thuộc tính để đơn giản hóa Account(Long id, String name). Bạn có thể muốn có một RowMapperđối tượng miền này.

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

Bây giờ bạn có thể sử dụng trình ánh xạ này trực tiếp trong một phương pháp để ánh xạ Accountcác đối tượng miền từ một truy vấn ( jtlà một JdbcTemplatephiên bản).

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

Tuyệt vời, nhưng bây giờ chúng tôi muốn vấn đề ban đầu của mình và chúng tôi sử dụng giải pháp ban đầu của tôi, sử dụng lại giải pháp RowMapperđể thực hiện ánh xạ cho chúng tôi.

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

Tuyệt vời, nhưng đây là một mô hình mà bạn có thể và sẽ muốn lặp lại. Vì vậy, bạn có thể tạo một phương thức nhà máy chung để tạo một phương thức mới ResultSetExtractorcho nhiệm vụ.

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

Tạo một ResultSetExtractorbây giờ trở nên tầm thường.

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

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

Tôi hy vọng điều này sẽ giúp cho bạn thấy rằng giờ đây bạn có thể dễ dàng kết hợp các phần một cách hiệu quả để làm cho miền của bạn đơn giản hơn.

CẬP NHẬT 2 : Kết hợp với một Tùy chọn cho các giá trị tùy chọn thay vì null.

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

Mà bây giờ khi được sử dụng có thể có những điều sau đây:

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

Đó không phải là một giải pháp tốt vì bạn đang dựa vào các ngoại lệ cho luồng điều khiển. Trong giải pháp của bạn, việc có các ngoại lệ là điều bình thường, việc có chúng trong nhật ký là điều bình thường.

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

giải pháp của tôi có thể không phải là giải pháp thanh lịch nhất nhưng ít nhất là hiệu quả của tôi. Bạn đưa ra một ví dụ về queryForObjectList thậm chí không phải là một tùy chọn với Jdbctemplate.
Byron

1
Chỉ có một nhược điểm ở đây là nếu kiểu trả về là một kiểu phức tạp, bạn sẽ xây dựng nhiều đối tượng và tạo một danh sách, cũng ResultSet.next()sẽ được gọi là không cần thiết. Sử dụng a ResultSetExtractorlà một công cụ hiệu quả hơn nhiều trong trường hợp này.
Brett Ryan

và điều gì sẽ xảy ra nếu không có giá trị là một tùy chọn, nhưng không có nhiều hơn một? Tôi có mẫu này thường xuyên và tôi muốn có một queryForOptionalObject trong Spring cho mục đích này.
Guillaume

7

Trên thực tế, bạn có thể chơi JdbcTemplatevà tùy chỉnh phương pháp của riêng mình theo ý muốn. Đề xuất của tôi là làm một cái gì đó như thế này:

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

Nó hoạt động như ban đầu jdbc.queryForObject, nhưng không có throw new EmptyResultDataAccessExceptionkhi nào size == 0.


@Abdull UserMapper implements RowMapper<String>.
Brett Ryan

Tôi nghĩ rằng đó là câu trả lời tốt nhất ở đây là nó mang lại cho cú pháp ngắn nhất
Stan Sokolov

DataAccessUtils.singleResult(...)là những gì tôi đang tìm kiếm. Thx
Drakes

7

Vì trả về null khi không có dữ liệu là điều tôi muốn làm thường xuyên khi sử dụng queryForObject nên tôi thấy việc mở rộng JdbcTemplate và thêm một phương thức queryForNullableObject tương tự như bên dưới.

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

}

Bây giờ bạn có thể sử dụng điều này trong mã của mình giống như cách bạn đã sử dụng queryForObject

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

Tôi muốn biết liệu có ai khác nghĩ rằng đây là một ý tưởng hay không?


1
Đó là, và nó sẽ là vào mùa xuân
Guillaume

6

Ok, tôi đã tìm ra nó. Tôi chỉ gói nó trong một lần bắt thử và gửi lại 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
Tôi không hiểu tại sao điều này lại tồi tệ đến vậy và tại sao bạn lại nhận được rất nhiều lời tán thành vì nó, hãy ủng hộ việc trở thành một người theo chủ nghĩa chính thống về nguyên tắc 'không có chương trình nào trong phạm vi ngoại lệ'. Tôi sẽ chỉ thay thế dấu vết printstack bằng một bình luận giải thích trường hợp và không làm gì khác.
Guillaume

4

Sử dụng Java 8 trở lên, bạn có thể sử dụng một Optionalvà Java Streams.

Vì vậy, bạn có thể chỉ cần sử dụng JdbcTemplate.queryForList()phương thức, tạo Luồng và sử dụng phương pháp Stream.findFirst()này sẽ trả về giá trị đầu tiên của Luồng hoặc giá trị trống 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();
}

Để cải thiện hiệu suất của truy vấn, bạn có thể nối thêm LIMIT 1 vào truy vấn của mình, do đó, không quá 1 mục được chuyển từ cơ sở dữ liệu.


1
Đẹp và sạch sẽ. Không có ifs hoặc lambdas bổ sung. Tôi thích nó.
BeshEater

2

Bạn có thể sử dụng một hàm nhóm để truy vấn của bạn luôn trả về một kết quả. I E

MIN(ID_NMB_SRZ)

1

Trong Postgres, bạn có thể thực hiện hầu hết mọi truy vấn giá trị đơn lẻ đều trả về giá trị hoặc null bằng cách gói nó:

SELECT (SELECT <query>) AS value

và do đó tránh được sự phức tạp trong trình gọi.


1

Vì getJdbcTemplate (). QueryForMap mong đợi kích thước tối thiểu là một nhưng khi trả về null, nó hiển thị EmptyResultDataAccesso sửa lỗi khi có thể sử dụng logic bên dưới

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

Tôi đã xử lý điều này trước đây và đã đăng trên các diễn đàn mùa xuân.

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

Lời khuyên mà chúng tôi nhận được là sử dụng một loại SQlQuery. Đây là một ví dụ về những gì chúng tôi đã làm khi cố gắng lấy một giá trị từ một DB có thể không có ở đó.

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

Trong DAO thì chúng ta chỉ gọi ...

Long id = findID.findObject(id);

Không rõ ràng về hiệu suất, nhưng nó hoạt động và gọn gàng.


0

Đối với Byron, bạn có thể thử điều này ..

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

để làm cho

    jdbcTemplate.queryForList(sql, String.class)

làm việc, hãy đảm bảo rằng jdbcTemplate của bạn thuộc loại

    org.springframework.jdbc.core.JdbcTemplate

0

Chúng tôi có thể sử dụng truy vấn thay vì queryForObject, sự khác biệt chính giữa truy vấn và queryForObject là danh sách trả về truy vấn của Đối tượng (dựa trên kiểu trả về của Trình ánh xạ hàng) và danh sách đó có thể trống nếu không nhận được dữ liệu nào từ cơ sở dữ liệu trong khi queryForObject luôn mong đợi chỉ một đối tượng duy nhất là được tìm nạp từ db không phải là giá trị rỗng hoặc nhiều hàng và trong trường hợp nếu kết quả là trống thì queryForObject ném EmptyResultDataAccessException, tôi đã viết một mã sử dụng truy vấn sẽ khắc phục sự cố của EmptyResultDataAccessException trong trường hợp kết quả rỗng.

----------


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 trả về một nulllà một giải pháp không tốt vì bây giờ bạn gặp vấn đề khi gửi và thông dịch nó tại client front end (có thể). Tôi đã gặp lỗi tương tự và tôi đã giải quyết nó bằng cách đơn giản trả lại a List<FooObject>. Tôi đã sử dụng JDBCTemplate.query().

Ở giao diện người dùng (ứng dụng web Angular), tôi chỉ cần kiểm tra danh sách và nếu nó trống (có độ dài bằng 0), hãy coi nó như không tìm thấy bản ghi.


-1

Tôi chỉ bắt được "EmptyResultDataAccessException" này

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

thì bạn có thể kiểm tra:

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

Chúng tôi có thể sử dụng truy vấn thay vì queryForObject
ABHAY JOHRI

1
Thường được coi là thực hành xấu để lạm dụng các trường hợp ngoại lệ như thế này. Các ngoại lệ không dành cho luồng logic chương trình có thể dự đoán được, chúng dành cho các tình huống ngoại lệ.
Chris Baker
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.