背景

上篇文章<模板方法与JdbcTemplate>中使用JdbcTemplate和RowMapper组合,可以轻松的查出List<T>的结果,翻看JdbcTemplate的api,发现有一个方法:List<T> queryForList(String sql, Class<T> elementType),上篇文章的用法是不是太复杂?是不是不用RowMapper,直接使用找个 List<T> queryForList(String sql, Class<T> elementType)一步就完成了?

结果是我还是too young too naive,我们来还原一下事件场景。 示例

准备工作

  • 表创建

CREATE TABLE student1 ( id int(11) NOT NULL AUTO_INCREMENT, first_name varchar(100) DEFAULT NULL, last_name varchar(100) DEFAULT NULL, age int(11) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

插入数据 在spring-jdbc项目中增加mysql的依赖:

  • JdbcTemplate示例 定义JavaBean 配置DataSource和JdbcTemplate 测试程序: 多么完美的程序呀 运行结果:

Exception in thread "main" org.springframework.jdbc.IncorrectResultSetColumnCountException: Incorrect column count: expected 1, actual 4 at org.springframework.jdbc.core.SingleColumnRowMapper.mapRow(SingleColumnRowMapper.java:110) at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94) at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:1) at org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:440) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:376) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:452) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:462) at org.springframework.jdbc.core.JdbcTemplate.queryForList(JdbcTemplate.java:485) at com.davidwang456.test.JdbcTemplateTest.main(JdbcTemplateTest.java:31)

这到底是什么鬼?难道是spring的bug?

真相大白 从上面可以看到报错出现在SingleColumnRowMapper.mapRow()方法,原因是结果集只能返回一列: 为了继续探查下去,我们将测试程序修改为返回一个列,例如id,此时的T 返回值要改完Integer了,因为返回的是Id的列表: 再次运行程序,此时返回的结果如下:

1 2 3 4 5 7 8

为了验证我们的判断,我们再尝试获取first_name字段: 结果显示:

ww www wwww wwwwww www www www

可以看到,其实queryForList(String sql, Class<T> elementType)的elementType只支持返回一个列的list值,只支持的数据库的类型在JdbcUtils#getResultSetValue()方法中: 拓展 如果使用JdbcTemplate.queryForList(String sql),不加返回类型,返回的是什么样子的呢?

@Override public List<Map<String, Object>> queryForList(String sql) throws DataAccessException { return query(sql, getColumnMapRowMapper()); }

我们来练练手吧 运行结果如下:

[id, first_name, last_name, age]------[1, ww, test, 7] [id, first_name, last_name, age]------[2, www, test111, 17] [id, first_name, last_name, age]------[3, wwww, testyyy, 27] [id, first_name, last_name, age]------[4, wwwwww, testyyyxxxx, 37] [id, first_name, last_name, age]------[5, www, baidu.com, 23] [id, first_name, last_name, age]------[7, www, baidu.com, 23] [id, first_name, last_name, age]------[8, www, baidu.com, 23]

返回的是以一列为一个Map的键值对集合。 总结

  • 在JdbcTemplate模板方法中,RowMapper的实现类起到了决定返回值形式的作用,如上面的示例,queryForList带返回类型的,使用的是SingleColumnRowMapper,仅能返回一列值的集合。

@Override public <T> List<T> queryForList(String sql, Class<T> elementType) throws DataAccessException { return query(sql, getSingleColumnRowMapper(elementType)); }

  • 而不带参数queryForList方法,返回值的实现,使用的是ColumnMapRowMapper,返回的是以一行的值为一个Map键值对,其中键为列名,值为列值。

@Override public List<Map<String, Object>> queryForList(String sql) throws DataAccessException { return query(sql, getColumnMapRowMapper()); }

  • 在使用JdbcTemplate方法时一定要注意到底使用的是RowMapper的哪个实现类。 如果仅仅想返回数据库表对应javaBean的列表,建议使用jdbcTemplate.query方法接口

List<T> query(String sql, RowMapper<T> rowMapper)

使用自定义RowMapper的实现类,例如:StudentMapper

  • 这里要吐槽一下queryForList找个api的设计,起了一个如此容易引起误解的名称。