query功能的实现

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源码深度解析》 郝佳 编著: 

原文地址:https://www.cnblogs.com/Joe-Go/p/10250920.html