字符串的 Jdbc 模板查询:空结果数据访问异常:不正确的结果大小:预期 1,实际 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'";
        cert = (String) jdbc.queryForObject(sql, String.class); 
        return cert;
    }

在我的场景中,我的查询完全有可能不被命中,所以我的问题是如何绕过以下错误消息。

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

在我看来,我应该只返回一个 null 而不是抛出异常。 我该如何解决这个问题?

在 JdbcTemplate 中,queryForIntqueryForLong queryForObject所有这些方法都期望执行的查询将返回一行且仅返回一行。如果没有行或超过一行,将导致IncorrectResultSizeDataAccessException .现在正确的方法是不要捕获此异常或EmptyResultDataAccessException,但请确保您使用的查询应仅返回一行。 如果根本不可能,则改用query方法。

List<String> strLst = getJdbcTemplate().query(sql, new RowMapper<String>() {
    public String 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 element
         // either return 1st element or throw an exception
}
您也可以

使用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 lambda 配合使用非常出色,但您可以轻松地使用静态类来实现相同的目标。

虽然问题是关于简单类型的,但这些示例可作为提取域对象的常见情况的指南。

首先。假设您有一个帐户对象,为简单起见,有两个属性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:与可选值组合,而不是 null。

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

好的,我想通了。 我只是把它包装在一个尝试捕获中并发回 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;
    }

由于在没有数据时返回 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);

我很想知道是否有其他人认为这是一个好主意?

使用 Java

8 或更高版本,您可以使用 Optional 和 Java Streams。

因此,您可以简单地使用 JdbcTemplate.queryForList() 方法,创建一个 Stream 并使用 Stream.findFirst() 它将返回 Stream 的第一个值或空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 个。

实际上,您可以使用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工作,但在size == 0时没有throw new EmptyResultDataAccessException

你可以这样做:

String cert = DataAccessUtils.singleResult(
    jdbcTemplate.queryForList(
        "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN where ID_STR_RT = :id_str_rt and ID_NMB_SRZ = :id_nmb_srz",
        new MapSqlParameterSource()
            .addValue("id_str_rt", "999")
            .addValue("id_nmb_srz", "60230009999999"),
        String.class
    )
)

DataAccessUtils.singleResult + queryForList

  • 对于空结果返回空值
  • 如果正好找到 1 行,则返回单个结果
  • 如果找到超过 1 行,则引发异常。主键/唯一索引搜索不应发生

DataAccessUtils.singleResult

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

您可以使用组函数,以便查询始终返回结果。即

MIN(ID_NMB_SRZ)

在 Postgres 中,您可以通过包装它来使几乎任何单值查询返回值或 null:

SELECT (SELECT <query>) AS value

从而避免调用方的复杂性。

我们可以使用查询而不是 queryForObject,查询和 queryForObject 之间的主要区别在于查询返回 Object(基于行映射器返回类型(的列表,并且如果没有从数据库接收到数据,则该列表可以为空,而 queryForObject 始终期望只从数据库中获取单个对象,既不为 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);
      }
  }

我之前处理过这个问题,并在春季论坛上发帖。

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

不清楚性能,但它有效且整洁。

对于拜伦,你可以试试这个。

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

    jdbcTemplate.queryForList(sql, String.class)

工作,确保您的 jdbc 模板类型为类型

    org.springframework.jdbc.core.JdbcTemplate

我直言,返回null是一个糟糕的解决方案,因为现在您在(可能的(前端客户端上发送和解释它时遇到了问题。我有同样的错误,我通过简单地返回一个List<FooObject>来解决它。我用了JDBCTemplate.query().

在前端(Angular Web 客户端(,我只需检查列表,如果它是空的(长度为零(,则将其视为未找到记录。

我在使用时遇到了同样的异常

if(jdbcTemplate.queryForMap("select * from table").size() > 0 ) /* resulted exception */ 

当更改为列表时,它已解决

if(jdbcTemplate.queryForList("select * from table").size() > 0) /* no exception */

我只是抓住了这个"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.
}

最新更新