Jdbctemplate query for string: EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

前端 未结 17 1130
执笔经年
执笔经年 2020-11-29 16:22

I am using Jdbctemplate to retrieve a single String value from the db. Here is my method.

    public String test() {
        String cert=null;
        Strin         


        
相关标签:
17条回答
  • 2020-11-29 17:01

    Ok, I figured it out. I just wrapped it in a try catch and send back 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;
        }
    
    0 讨论(0)
  • 2020-11-29 17:01

    to make

        jdbcTemplate.queryForList(sql, String.class)
    

    work, make sure your jdbcTemplate is of type

        org.springframework.jdbc.core.JdbcTemplate
    
    0 讨论(0)
  • 2020-11-29 17:02

    Since returning a null when there is no data is something I want to do often when using queryForObject I have found it useful to extend JdbcTemplate and add a queryForNullableObject method similar to below.

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

    You can now use this in your code the same way you used queryForObject

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

    I would be interested to know if anyone else thinks this is a good idea?

    0 讨论(0)
  • 2020-11-29 17:06

    You may also use a ResultSetExtractor instead of a RowMapper. Both are just as easy as one another, the only difference is you call 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;
            }
        });
    }
    

    The ResultSetExtractor has the added benefit that you can handle all cases where there are more than one row or no rows returned.

    UPDATE: Several years on and I have a few tricks to share. JdbcTemplate works superbly with java 8 lambdas which the following examples are designed for but you can quite easily use a static class to achieve the same.

    While the question is about simple types, these examples serve as a guide for the common case of extracting domain objects.

    First off. Let's suppose that you have an account object with two properties for simplicity Account(Long id, String name). You would likely wish to have a RowMapper for this domain object.

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

    You may now use this mapper directly within a method to map Account domain objects from a query (jt is a JdbcTemplate instance).

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

    Great, but now we want our original problem and we use my original solution reusing the RowMapper to perform the mapping for us.

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

    Great, but this is a pattern you may and will wish to repeat. So you can create a generic factory method to create a new ResultSetExtractor for the task.

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

    Creating a ResultSetExtractor now becomes trivial.

    private static final ResultSetExtractor<Account> EXTRACTOR_ACCOUNT =
            singletonExtractor(MAPPER_ACCOUNT);
    
    public Account getAccount(long id) {
        return jt.query(SELECT_ACCOUNT, EXTRACTOR_ACCOUNT, id);
    }
    

    I hope this helps to show that you can now quite easily combine parts in a powerful way to make your domain simpler.

    UPDATE 2: Combine with an Optional for optional values instead of null.

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

    Which now when used could have the following:

    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);
    }
    
    0 讨论(0)
  • 2020-11-29 17:07

    Actually, You can play with JdbcTemplate and customize your own method as you prefer. My suggestion is to make something like this:

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

    It works as the original jdbc.queryForObject, but without throw new EmptyResultDataAccessException when size == 0.

    0 讨论(0)
  • 2020-11-29 17:07

    I just catch this "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;
        }
    }
    

    then you can check:

    if(m == null){
        // insert operation.
    }else{
        // update operation.
    }
    
    0 讨论(0)
提交回复
热议问题