119

我正在使用 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 而不是抛出异常。我怎样才能解决这个问题?提前致谢。

4

18 回答 18

199

在 JdbcTemplate , queryForInt, 中queryForLongqueryForObject所有此类方法都期望执行的查询将返回一行且仅返回一行。如果您没有得到任何行或多于一行,则会导致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
}
于 2012-05-16T05:46:11.900 回答
53

您也可以使用 aResultSetExtractor而不是 a 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的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);
}
于 2013-05-06T00:44:53.430 回答
25

这不是一个好的解决方案,因为您依赖于控制流的异常。在您的解决方案中,出现异常是正常的,将它们放在日志中是正常的。

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);
    }
}
于 2012-05-15T20:07:26.293 回答
12

好的,我想通了。我只是将它包装在 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;
    }
于 2012-05-15T18:11:58.257 回答
8

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

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

于 2014-02-25T05:46:16.140 回答
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 EmptyResultDataAccessExceptionwhen size == 0

于 2013-07-12T08:54:06.637 回答
6

使用 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 个。

于 2019-08-26T18:22:50.067 回答
2

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

MIN(ID_NMB_SRZ)
于 2012-06-18T14:34:37.917 回答
2

由于 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;
}
于 2014-05-21T05:01:40.840 回答
2

你可以这样做:

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

  • 为空结果返回 null
  • 如果恰好找到 1 行,则返回单个结果
  • 如果找到超过 1 行,则抛出异常。主键/唯一索引搜索不应发生

DataAccessUtils.singleResult

于 2020-09-22T08:33:14.683 回答
1

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

SELECT (SELECT <query>) AS value

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

于 2014-07-05T04:43:10.067 回答
1

我们可以使用 query 代替 queryForObject,query 和 queryForObject 的主要区别在于查询返回的对象列表(基于 Row 映射器返回类型),如果没有从数据库接收到数据,则该列表可以为空,而 queryForObject 总是期望只有单个对象从 db 获取的既不是 null 也不是多行,如果结果为空,那么 queryForObject 会抛出 EmptyResultDataAccessException,我编写了一个使用查询的代码,该代码将在 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);
      }
  }
于 2017-11-07T07:26:32.417 回答
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);

性能不清楚,但它可以工作并且很整洁。

于 2014-03-25T18:28:02.813 回答
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> li = jdbcTemplate.queryForList(sql,String.class);
                return li.get(0).toString();
        }
于 2015-08-07T05:18:57.313 回答
0

使

    jdbcTemplate.queryForList(sql, String.class)

工作,确保你的 jdbcTemplate 是类型

    org.springframework.jdbc.core.JdbcTemplate
于 2015-08-19T13:45:14.263 回答
0

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

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

于 2020-01-21T18:13:59.567 回答
0

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

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

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

if(jdbcTemplate.queryForList("select * from table").size() > 0) /* no exception */
于 2021-04-01T15:52:09.663 回答
-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.
}
于 2016-11-16T13:39:10.420 回答