Fork me on GitHub
0%

使用 JdbcTemplate 的 queryForObject 方法时需注意的地方(一)

我们应该都使用过 JdbcTemplate 来查询数据库,一般用在配置了多数据源的情况下,在一个服务里面需要简单查询多个数据库的数据,这时通过 JdbcTemplate 来构建 SQL 查询数据库往往比较方便快捷些,不需要用封装的 repository 那一套东西。

举一个简单的例子,比如查询一个活动的浏览量。

1
2
3
4
5
6
private Long findByActivityId(String activityId) {
String selectSql = "select times_viewed from activity_stats where activity_id = ?";
Long timesViewed = this.jdbcTemplate.queryForObject(selectSql, Long.class,
activityId);
return timesViewed;
}

其实我们咋一看上面这段代码,好像也没多大问题,只是一个普通的查询而已。在有数据的情况下,经过测试,程序也运行正常。但是如果你把你要查询的那个活动 id 的那条数据删除,这时再去测试会发现程序抛异常了,抛的异常类是 EmptyResultDataAccessException,从类的名字我们就可以看出,查询结果为空异常。

这里为什么会产生这种情况呢,我们深入查看下源码就知道了。

1
2
3
4
5
6
public <T> T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
throws DataAccessException {

List<T> results = getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper);
return DataAccessUtils.nullableSingleResult(results);
}

我们点进去查看 queryForObject 的方法体可以看到,在返回查询结果前调用了 DataAccessUtils 类的 nullableSingleResult 方法,那么再继续点进去查看 nullableSingleResult 的方法体:

1
2
3
4
5
6
7
8
9
public static <T> T nullableSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
if (CollectionUtils.isEmpty(results)) {
throw new EmptyResultDataAccessException(1);
} else if (results.size() > 1) {
throw new IncorrectResultSizeDataAccessException(1, results.size());
} else {
return results.iterator().next();
}
}

从 nullableSingleResult 的方法体中可以看到,当查询的结果为空的时候,手动抛了一个 EmptyResultDataAccessException 异常,同时我们还可以看到当查询结果记录数大于 1 时,手动抛了一个 IncorrectResultSizeDataAccessException 异常,这时候我们就要注意了,如果当前我们要执行的 SQL 的查询结果可能为空,或者结果集可能大于 1 时就需要根据我们的业务需求来进行手动处理这两个异常,不然程序会由于异常无法进行下去。

在这里个人觉得结果大于 1 的情况抛出异常还能理解,但是结果为空,其实在很多情况下我们都是希望代码正常运行下去,而不是直接抛出异常,有可能我们还有别的业务需要执行。所以不太清楚这里为什么需要做这个处理。

对于结果为空时抛出异常的情况,处理方式也很简单,在调用 queryForObject 方法的地方用 try catch 包起来,对 EmptyResultDataAccessException 异常进行捕获,同时忽略异常信息就好。

1
2
3
4
5
6
7
8
9
10
11
private Long queryByActivityId(String activityId) {
String selectSql = "select times_viewed from activity_stats where activity_id = ?";
Long timesViewed = null;
try {
timesViewed = this.jdbcTemplate.queryForObject(selectSql, Long.class,
activityId);
}
catch (EmptyResultDataAccessException ignored) {
}
return timesViewed;
}

同理,如果对结果集可能大于 1 的情况也要特殊处理的话,我们就直接捕获 IncorrectResultSizeDataAccessException 异常就好,因为你可以看到 EmptyResultDataAccessException 是继承 IncorrectResultSizeDataAccessException 的。所以就直接捕获 IncorrectResultSizeDataAccessException 就好。

1
2
3
4
5
6
7
8
9
10
11
private Long queryByActivityId(String activityId) {
String selectSql = "select times_viewed from activity_stats where activity_id = ?";
Long timesViewed = null;
try {
timesViewed = this.jdbcTemplate.queryForObject(selectSql, Long.class,
activityId);
}
catch (IncorrectResultSizeDataAccessException ignored) {
}
return timesViewed;
}

这里再提下 JdbcTemplate 和 NamedParmeterJdbcTemplate 的主要区别:
对于 JdbcTemplate,sql 的查询条件参数是使用占位符 ? 表示,受到了顺序的限制,如果是需要多个参数,在传入数组参数的时候,必须按照占位符的顺序传入参数,一旦传入的顺序错误就可能会造成非预期的结果。

而 NamedParmeterJdbcTemplate,在 sprintJdbc 框架中,通过这个 NameParameterJdbcTemplate 使用具名参数的方式来绑定 Sql 参数。具名参数的格式为 “ :parameterName”,多个具名参数通过 Map key value 的方式传入,key 和 parameterName 保持一致,这种情况下就和参数的顺序没有关系了,主要是通过 Map 的 key 来取值。

 wechat
扫描上面图中二维码关注微信公众号