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

  • 相关阅读:
    合同主体列表添加两条合同主体,返回合并支付页面,支付总弹"请选择合同主体",删除后,竟然还能支付(改合并支付页面的字段状态)
    (TODO:)下载图片,报错:warning: could not load any Objective-C class information from the dyld shared cache. This will significantly reduce the quality of type information available.
    GCD死锁 多线程
    iOS知识总结
    快速排序,冒泡排序,选择排序
    fight
    3D Touch
    Xcode 调试技巧
    右滑退出手势及隐藏导航栏存在的风险
    C语言-第5课
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/10250920.html
Copyright © 2020-2023  润新知