Jdbctemplate 字符串查询: EmptyResultDataAccessException: 不正确的结果大小: 预期1,实际0

我使用 Jdbctemplate 从 db 中检索一个 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

It would seem to me that I should just get back a null instead of throwing an exception. How can I fix this? Thanks in advance.

296818 次浏览

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

这不是一个好的解决方案,因为控制流依赖于异常。在您的解决方案中,获得异常是正常的,在日志中包含异常是正常的。

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、 queryForIntqueryForLongqueryForObject中,所有这些方法都期望执行的查询只返回一行。如果没有行或者有多行,那么将导致 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
}

可以使用 group 函数,以便查询始终返回结果。 也就是说

MIN(ID_NMB_SRZ)

您也可以使用 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;
}
});
}

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

更新 : 几年过去了,我有一些技巧可以分享。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域对象(jtJdbcTemplate实例)。

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

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 : 结合 可以选择获得可选值而不是 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);
}

实际上,您可以使用 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 EmptyResultDataAccessExceptionsize == 0

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

我很想知道有没有人觉得这是个好主意?

I dealt with this before & had posted in the spring forums.

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

Not clear on performance, but it works and is neat.

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

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

SELECT (SELECT <query>) AS value

and hence avoid complexity in the caller.

拜伦,你可以试试这个。

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

to make

    jdbcTemplate.queryForList(sql, String.class)

工作时,请确保 jdbcTemplate 的类型为

    org.springframework.jdbc.core.JdbcTemplate

我只是捕捉到这个“ 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 的主要区别在于,如果没有数据从数据库接收到,那么这个列表可以是空的,而 queryForObject 总是希望从 db 获取单个对象,既不是 null 也不是多行,如果 result 是空的,那么 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);
}
}

使用 Java8或更高版本,您可以使用 Optional和 JavaStreams。

所以你可以简单地使用 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个。

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

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

你可以这样做:

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

结果 + QueryForList:

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

单结果

我在使用

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

当更改为列表时解决

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