แบบสอบถาม Jdbctemplate สำหรับสตริง: EmptyResultDataAccessException: ขนาดผลลัพธ์ไม่ถูกต้อง: คาดว่า 1, 0 จริง


108

ฉันใช้ Jdbctemplate เพื่อดึงค่า String เดียวจาก db นี่คือวิธีการของฉัน

    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

สำหรับฉันแล้วดูเหมือนว่าฉันควรจะคืนค่าว่างแทนที่จะโยนข้อยกเว้น ฉันจะแก้ไขปัญหานี้ได้อย่างไร? ขอบคุณล่วงหน้า.

คำตอบ:


183

ใน 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()จะถูกเรียกโดยไม่จำเป็น การใช้ a ResultSetExtractorเป็นเครื่องมือที่มีประสิทธิภาพมากกว่าในกรณีนี้
Brett Ryan

3
มีวงเล็บที่ขาดหายไปในนิยามคลาสที่ไม่ระบุชื่อ - RowMapper ใหม่()
Janis Koluzs

ฉันอยู่กับ Brett เกี่ยวกับเรื่องนี้ ResultSetExtractor สะอาดกว่า :)
laher

2
สวัสดี @Rakesh ทำไมไม่เพียง แต่return nullในcatch(EmptyResultDataAccessException exception){ return null; }?
Vishal Zanzrukia

1
เฮ้! ฉันถามได้ไหมว่าทำไม 'ตอนนี้วิธีที่ถูกต้องคือไม่จับข้อยกเว้นนี้' โดยพิจารณาว่าคุณกำลังใช้ queryForObject หรือไม่ จะเกิดอะไรขึ้นกับการจับข้อยกเว้นในกรณีของ queryForObject ขอบคุณ :)
Michael Stokes

48

คุณยังสามารถใช้ a 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 lambdas ซึ่งตัวอย่างต่อไปนี้ออกแบบมาเพื่อ แต่คุณสามารถใช้คลาสแบบคงที่เพื่อให้ได้สิ่งเดียวกัน

แม้ว่าคำถามจะเกี่ยวกับประเภทง่ายๆ แต่ตัวอย่างเหล่านี้ใช้เป็นแนวทางสำหรับกรณีทั่วไปของการแยกออบเจ็กต์โดเมน

ก่อนอื่น 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);
}

22

นั่นไม่ใช่วิธีแก้ปัญหาที่ดีเพราะคุณต้องอาศัยข้อยกเว้นสำหรับขั้นตอนการควบคุม ในโซลูชันของคุณเป็นเรื่องปกติที่จะได้รับข้อยกเว้นซึ่งเป็นเรื่องปกติที่จะมีไว้ในบันทึก

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
Byron

1
ข้อเสียเปรียบเพียงอย่างเดียวคือถ้าประเภทการส่งคืนเป็นประเภทที่ซับซ้อนคุณจะต้องสร้างวัตถุหลายชิ้นและสร้างอินสแตนซ์รายการก็ResultSet.next()จะถูกเรียกโดยไม่จำเป็น การใช้ a ResultSetExtractorเป็นเครื่องมือที่มีประสิทธิภาพมากกว่าในกรณีนี้
Brett Ryan

และถ้าไม่มีค่าเป็นตัวเลือก แต่ไม่มีมากกว่าหนึ่ง? ฉันมีรูปแบบนี้บ่อยๆและฉันต้องการให้มี queryForOptionalObject ใน Spring เพื่อจุดประสงค์นี้
Guillaume

8

โอเคฉันคิดออกแล้ว ฉันแค่ห่อลองจับแล้วส่งกลับค่าว่าง

    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

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ใด


@ อับดุลUserMapper implements RowMapper<String>.
Brett Ryan

ฉันคิดว่ามันเป็นคำตอบที่ดีที่สุดที่นี่เพราะมันให้ไวยากรณ์ที่สั้นที่สุด
Stan Sokolov

DataAccessUtils.singleResult(...)คือสิ่งที่ฉันกำลังมองหา Thx
Drakes

7

เนื่องจากส่งคืนค่าว่างเมื่อไม่มีข้อมูลเป็นสิ่งที่ฉันต้องการทำบ่อยๆเมื่อใช้ 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
มันเป็นและควรจะเป็นในฤดูใบไม้ผลิ
กิลลา

4

การใช้ Java 8 ขึ้นไปคุณสามารถใช้Optionalและ Java Streams

ดังนั้นคุณสามารถใช้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
สวยและสะอาด ไม่มี ifs หรือ lambdas เพิ่มเติม ฉันชอบมัน.
BeshEater

2

คุณสามารถใช้ฟังก์ชันกลุ่มเพื่อให้แบบสอบถามของคุณส่งคืนผลลัพธ์เสมอ กล่าวคือ

MIN(ID_NMB_SRZ)

1

ใน Postgres คุณสามารถสร้างแบบสอบถามค่าเดียวเกือบทั้งหมดส่งคืนค่าหรือค่าว่างโดยการตัด:

SELECT (SELECT <query>) AS value

และด้วยเหตุนี้จึงหลีกเลี่ยงความซับซ้อนในผู้โทร


1

เนื่องจาก 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;
}

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

สำหรับ 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

เราสามารถใช้ query แทน queryForObject ความแตกต่างที่สำคัญระหว่าง query และ queryForObject คือรายการส่งคืนการสืบค้นของ Object (ขึ้นอยู่กับ Row mapper return type) และรายการนั้นจะว่างเปล่าหากไม่มีการรับข้อมูลจากฐานข้อมูลในขณะที่ queryForObject มักจะคาดหวังว่าจะมีเพียงวัตถุเดียวเท่านั้น ดึงมาจาก db ไม่ใช่ null หรือหลายแถวและในกรณีที่ผลลัพธ์ว่างจากนั้น 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);
      }
  }

0

IMHO ส่งคืน a 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.
}

เราสามารถใช้ query แทน queryForObject
ABHAY JOHRI

1
โดยปกติถือว่าเป็นการปฏิบัติที่ไม่ดีในการละเมิดข้อยกเว้นเช่นนี้ ข้อยกเว้นไม่ใช่สำหรับโฟลว์ลอจิกของโปรแกรมที่คาดเดาได้ แต่มีไว้สำหรับสถานการณ์พิเศษ
Chris Baker
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.