上一篇文章中提到了 JdbcTemplate 的 queryForObject 方法在使用时需要注意的地方,其实这里还有一个地方也是我们在使用时容易忽略的,为了避免踩坑,这里我也一并提出来。
同样的,继续用活动的状态表来举例,在上次的例子中我们是通过活动 id 查询活动的浏览量这一列数据,但是如果我们想要一并查出活动的浏览量,作品数和用户数。先让我们同样使用 queryForObject 方法,然后把 SQL 改成查询所有列的数据,返回类型改成实体类。
1 2 3 4 5 6
| private Long findByActivityId(String activityId) { String selectSql = "select * from activity_stats where activity_id = ?"; ActivityStat activityStat = this.jdbcTemplate.queryForObject(selectSql, ActivityStat.class, activityId); return timesViewed; }
|
执行之后会发现报错了,错误信息如下:
从字面意思看是提示查询结果列的数量不正确,期望值是 1,但实际是 4。
是不是感觉有点奇怪,看代码好像也没啥问题,我们找到报错的位置看看为什么会出现这种结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public T mapRow(ResultSet rs, int rowNum) throws SQLException { ResultSetMetaData rsmd = rs.getMetaData(); int nrOfColumns = rsmd.getColumnCount(); if (nrOfColumns != 1) { throw new IncorrectResultSetColumnCountException(1, nrOfColumns); } else { Object result = this.getColumnValue(rs, 1, this.requiredType); if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) { try { return this.convertValueToRequiredType(result, this.requiredType); } catch (IllegalArgumentException var7) { throw new TypeMismatchDataAccessException("Type mismatch affecting row number " + rowNum + " and column type '" + rsmd.getColumnTypeName(1) + "': " + var7.getMessage()); } } else { return result; } } }
|
从源码中我们可以看到在将结果集映射到我们指定的实体类型的时候有个对结果集的 column count 的判断,当 column count 不等于 1 的时候就会抛出 IncorrectResultSetColumnCountException 异常。
1 2 3
| public <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args) throws DataAccessException { return this.queryForObject(sql, args, this.getSingleColumnRowMapper(requiredType)); }
|
其实从 queryForObject 方法的方法体也可以看出,里面调用了一个叫 getSingleColumnRowMapper 的方法,然后将我们指定的返回数据类型传进去,而方法的名字就是体现了方法的含义,返回一个单列的 row mapper。也就是 SingleColumnRowMapper 这个类,它实现了 RowMapper 接口,实现了接口里面的 mapRow 方法,也就是上面我们看到的报错的地方。
这时我们应该可以知道对于 queryForObject 方法当我们指定返回值类型的时候,它只适合用来查单列结果,也就是类似于下面的 SQL:
1 2
| select column1 from table; select count(*) from table;
|
再回到 queryForObject 方法,间接也说明我们设置返回类型的时候只能指定基本数据类型,而不能指定我们自定义的复杂数据类型。
但如果我们依然需要查询结果并返回我们自定义的数据类型的时候,我们可以使用 queryForObject 重载的方法,自定义 RowMapper,自己实现 mapRow 方法,在方法里面取出结果映射到实体类就可以了:
1 2 3 4 5 6 7 8 9 10 11
| public ActivityStat findById(Long id) { String sql = "select * from activity_stats where activity_id = ?"; return this.jdbcTemplate.queryForObject(sql, new Object[]{id}, (rs, i) -> { ActivityStat activityStat = new ActivityStat(); activityStat.setActivityId(rs.getLong("activity_id")); activityStat.setTimesViewed(rs.getLong("times_viewed")); activityStat.setWorksCount(rs.getLong("works_count")); activityStat.setUserCount(rs.getLong("user_count")); return activityStat; }); }
|
这里其实就是一个坑,稍不注意就会觉得代码好像也没啥问题,怎么会报错呢,同样的对于 queryForList 方法也会有这个问题,如果我们使用指定返回类型的话也是只能查询单列结果,需要返回多列时也需要我们自定义 RowMapper,感兴趣的可以自己去试试看。