Jdbctemplate查询string:EmptyResultDataAccessException:不正确的结果大小:预计1,实际0

我正在使用Jdbctemplate从数据库检索单个string值。 这是我的方法。

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 

在我看来,我应该回来一个空,而不是抛出一个exception。 我怎样才能解决这个问题? 提前致谢。

在JdbcTemplate中,queryForInt,queryForLong,queryForObject所有这些方法都希望执行的查询将返回一个且只有一行。 如果您没有行或多行将导致IncorrectResultSizeDataAccessException 。 现在正确的方法是不捕获这个exception或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. } 

您也可以使用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类似地工作,下面的例子是为了devise而成的,但是你可以很容易地使用一个静态类来达到同样的效果。

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

首先, 假设您有一个帐户对象,其中包含两个用于简化Account(Long id, String name)属性。 你可能希望有一个RowMapper的这个域对象。

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

您现在可以直接在方法中使用此映射器来映射来自查询的Account域对象( jtJdbcTemplate实例)。

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

太好了,但是现在我们想要我们原来的问题,我们使用我的原始解决scheme重新使用RowMapper来为我们执行映射。

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

太好了,但是这是一个你可能并希望重复的模式。 因此,您可以创build一个通用工厂方法来为该任务创build一个新的ResultSetExtractor

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

创buildResultSetExtractor现在变得微不足道了。

 private static final ResultSetExtractor<Account> EXTRACTOR_ACCOUNT = nullableExtractor(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 = nullableOptionalExtractor(MAPPER_DISCOUNT); public double getDiscount(long accountId) { return jt.query(SELECT_DISCOUNT, EXTRACTOR_DISCOUNT, accountId) .orElse(0.0); } 

这不是一个好的解决scheme,因为你依赖于控制stream的exception。 在你的解决scheme中,获取exception是正常的,在日志中使用它们是正常的。

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

其实,你可以玩JdbcTemplate并根据自己的喜好自定义你自己的方法。 我的build议是做这样的事情:

 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

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

我有兴趣知道是否有人认为这是一个好主意?

好的,我明白了。 我只是把它包装在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; } 

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

 MIN(ID_NMB_SRZ) 

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

 SELECT (SELECT <query>) AS value 

并因此避免了呼叫者的复杂性。

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

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

我们收到的build议是使用一种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) 

工作,确保你的jdbcTemplate是types的

  org.springframework.jdbc.core.JdbcTemplate 

由于getJdbcTemplate()。queryForMap期望最小的大小之一,但是当它返回null它显示EmptyResultDataAccesso修复时,可以使用下面的逻辑

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

我只是赶上这个“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. } 

我们可以使用查询而不是queryForObject,query和queryForObject之间的主要区别是Object的查询返回列表(基于Row mapper返回types),如果没有从数据库接收到数据,那么列表可以是空的,而queryForObject总是只期望单个对象从数据库中获取既不空也不是多行,如果结果为空然后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); } }