query功能的实现
上一篇文章介绍了update方法的功能实现,那么杂数据库操作中查找操作也是使用率非常高的函数,同样我们也需要了解它的实现过程。使用方法如下:
List<User> list = jdbcTemplate.query("select * from user",new UserRowMapper());
跟踪jdbcTemplate中的query方法:
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper))); }
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { logger.debug("Executing SQL query [" + sql + "]"); } /** * Callback to execute the query. */ class QueryStatementCallback implements StatementCallback<T>, SqlProvider { @Override @Nullable public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; try { rs = stmt.executeQuery(sql); return rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); } } @Override public String getSql() { return sql; } } return execute(new QueryStatementCallback()); }
可以看出整体的套路与update差不多的,只不过在回调类PreparedStatementCallback的实现中使用的是ps.executeQuery()执行查询操作,而且在返回方法上也做了一些额外的处理。
rse.extractData(rs)方法负责将结果进行封装并转换至POJO,rse当前代表的类为RowMapperResultSetExtractor,而在构造RowMapperResultSetExtractor的时候我们又将自定义的rowMapper设置了进去,调用的代码如下:
public List<T> extractData(ResultSet rs) throws SQLException { List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>()); int rowNum = 0; while (rs.next()) { results.add(this.rowMapper.mapRow(rs, rowNum++)); } return results; }
上面的代码并没有上面复杂的逻辑,只是对返回结果进行遍历并以此使用rowMapper进行转换。
前面讲述的update和query的方法,使用的都是SQL中带有参数的,也就是带有“?”的,那么还有一种不带有“?” 的,Spring使用的是另外一种处理方式。
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper))); }
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { logger.debug("Executing SQL query [" + sql + "]"); } /** * Callback to execute the query. */ class QueryStatementCallback implements StatementCallback<T>, SqlProvider { @Override @Nullable public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; try { rs = stmt.executeQuery(sql); return rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); } } @Override public String getSql() { return sql; } } return execute(new QueryStatementCallback()); }
与之前的query方法最大的不同是少了参数以及参数类型的传递,自然也就少了PreparedStatementSetter类型的封装,既然少了PreparedStatementSetter类型的传入,调用的execute方法自然也就会有所变化了。
public <T> T execute(StatementCallback<T> action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(obtainDataSource()); Statement stmt = null; try { stmt = con.createStatement(); applyStatementSettings(stmt); T result = action.doInStatement(stmt); handleWarnings(stmt); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. String sql = getSql(action); JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw translateException("StatementCallback", sql, ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } }
这个execute与之前的execute并无太大的差别,都是做了一些常规的处理,诸如获取连接、释放连接等,但是,有一个地方是不一样的,就是Statement的创建。这里直接使用connection创建,而带有参数的SQL使用的是PreparedStatementCreator类来创建。一个是普通的Statement,另一个是PreparedStatement,两者究竟有何区别呢?
PreparedStatement接口继承Statement,并与之在两方面有所不同。
❤ PreparedStatement实例包含以编译的SQL语句,这就是使语句“准备好”。包含于PreparedStatement对象中的SQL语句可具有一个或者多个IN参数。IN参数的值在SQL语句创建时未被指定。相反的,该语句为每个IN参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句被执行之前,通过适当的setXXX方法来提供。
❤ 由于PreparedStatement对象已经预编译过,所以其执行速度要快于Statement对象。因此,多次执行的SQL语句经常创建为PreparedStatement对象,以提高效率。
作为Statement的子类,PreparedStatement继承了Statement的所有功能。另外,它还添加了一整套方法,用于设置发送给数据库以取代IN参数占位符的值,同时,三种方法execute、executeQuery、executeUpdate已被更改以使之不再需要参数。这些方法的Statement形式(接收SQL语句参数的形式)不应该用于PreparedStatement对象。
queryForObject
Spring中不仅仅为我们提供了query方法,还在此基础上做了封装,提供了不同类型的query方法。
我们以queryForObject为例,来讨论一下Spring是如何在返回结果的基础上进行封装的。
public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException { return queryForObject(sql, getSingleColumnRowMapper(requiredType)); }
public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException { List<T> results = query(sql, rowMapper); return DataAccessUtils.nullableSingleResult(results); }
其实最大的不同还是对于RowMapper的使用,getSingleColumnRowMapper类中的mapRow:
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); } // 抽取第一个结果进行处理 Object result = getColumnValue(rs, 1, this.requiredType); if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) { // Extracted value does not match already: try to convert it. try { return (T) convertValueToRequiredType(result, this.requiredType); } catch (IllegalArgumentException ex) { throw new TypeMismatchDataAccessException( "Type mismatch affecting row number " + rowNum + " and column type '" + rsmd.getColumnTypeName(1) + "': " + ex.getMessage()); } } return (T) result; }
convertValueToRequiredType:转换对应的类型。
protected Object convertValueToRequiredType(Object value, Class<?> requiredType) { if (String.class == requiredType) { return value.toString(); } else if (Number.class.isAssignableFrom(requiredType)) { if (value instanceof Number) { // 将原始Number类型的实体转换为Number类 return NumberUtils.convertNumberToTargetClass(((Number) value), (Class<Number>) requiredType); } else { //将String转换为Number类 return NumberUtils.parseNumber(value.toString(),(Class<Number>) requiredType); } } else if (this.conversionService != null && this.conversionService.canConvert(value.getClass(), requiredType)) { return this.conversionService.convert(value, requiredType); } else { throw new IllegalArgumentException( "Value [" + value + "] is of type [" + value.getClass().getName() + "] and cannot be converted to required type [" + requiredType.getName() + "]"); } }
参考:《Spring源码深度解析》 郝佳 编著: