【问题标题】:Large list returned from a SimpleJdbcTemplate query从 SimpleJdbcTemplate 查询返回的大列表
【发布时间】:2011-09-30 16:11:48
【问题描述】:

这是我的问题:在我的 Java 程序中,我使用 Spring 中的 SimpleJdbcTemplate 类从数据库中获得了一个(非常)大的事件列表。

List<Event> events = 
            this.simpleJdbcTemplate.query(myQuery,
            myMapper(), 
            new Object[] {
                    filter.getFirst(),
                    filter.getSecond(),
                    filter.getThird()}
            );

问题是该列表可能包含类似 600,000 个事件...因此使用大量内存(并且还需要时间来处理)。

但是,我真的不需要一次检索所有事件。实际上我希望能够遍历列表,只读取几个事件(链接到特定的 KEY_ID - sql 查询 myQuery 由 KEY_ID 排序),处理它们并最终返回迭代,让垃圾收集器摆脱以前的和已经处理的事件,这样我就不会超过一定的内存量。

有没有使用 Spring 库(或任何库)的好方法?

干杯, 瓦基姆沙尔。

【问题讨论】:

  • 顺便说一句:使用 SimpleJdbcTemplate,您不必像所有相关方法 have vararg parameters 一样将参数包装在 Object 数组中。 (当然编译器在内部做同样的事情,但你的代码变得更具可读性)

标签: java sql spring jdbc spring-jdbc


【解决方案1】:

我认为您的问题的一部分是您的查询是一次执行的,并且您得到的结果集是一大块,占用了内存和网络带宽。除了需要一种方法来遍历结果集之外,您还需要一种方法来一次从数据库中取回结果。 看看this answer about lazy-loading resultsets。看起来您可以结合使用 ResultSetExtractor 设置提取大小,并可能获得所需的行为(取决于数据库)。

【讨论】:

    【解决方案2】:

    您应该构造您的 SQL 查询以返回以特定数字开头的有限项集。它是特定于数据库的操作(在 Oracle 和 MySql 中,您将以某种形式操作 rownum)。然后你重复调用增加起始编号,直到处理完所有元素。

    Oracle 示例

    SELECT * FROM ([your query]) WHERE rownum>=[start number] 
                                   AND rownum<[start number + chunk size];
    

    【讨论】:

    • 这个解决方案是否意味着对数据库的调用与 KEY_ID 一样多?目的也是只调用一次数据库:调用本身需要 30 秒,所以我想迭代已经生成的结果,如果可能的话,不在本地检索所有数据:)
    • 应该是number of rows/chunk size 查询。单个查询可能比单个查询更快。不过,可能有更好的解决方案。试一试,看看会发生什么。
    • +1 表示“看看会发生什么”。我的猜测是,使用单查询方法,由于底层查询不是惰性的,无论您如何提取结果集,都会导致内存膨胀和可能的网络饱和。但试试看。
    • @Nathan 我的观察结果是,大量结果集查询的大量时间用于传输数据。
    • 实际上,数据库很大,我知道对特定 KEY_ID 的请求(带有 WHERE 子句)与所有 KEY_ID 的请求所用时间相同......另外我不知道提前块大小...
    【解决方案3】:

    如果我理解正确,您想迭代结果集,但对构建完整的结果列表不感兴趣。

    只需使用query method with a ResultSetExtractor 作为参数。 ResultSetExtractor 可以使用您的映射器将当前行转换为Event。将每个事件放入一个列表中,直到您到达不同的 KEY_ID 或结果集的末尾,然后继续您的事件列表并清除列表。

    【讨论】:

    • 感谢您的回答,我可能会尝试这个,返回一个 Iterator> :)
    【解决方案4】:

    也许下面的代码对你有用?

    protected <T> List<T> queryPagingList(final String query, final PagingQueryContext context, final ParameterizedRowMapper<T> mapper, final SqlParameter... parameters) throws DataAccessException {
        final Integer count = context.getCount();
        final Integer beginIndex = context.getBeginIndex();
        final List<SqlParameter> parameterList = Arrays.asList(parameters);
        final PreparedStatementCreatorFactory preparedStatementCreatorFactory = new PreparedStatementCreatorFactory(query, parameterList);
        preparedStatementCreatorFactory.setResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
        preparedStatementCreatorFactory.setNativeJdbcExtractor(new NativeJdbcExtractorAdapter() {
            @Override
            public PreparedStatement getNativePreparedStatement(final PreparedStatement ps) throws SQLException {
                ps.setFetchSize(count + 1);
                ps.setMaxRows((beginIndex * count) + 1);
                return ps;
            }
    
            @Override
            public Statement getNativeStatement(final Statement stmt) throws SQLException {
                stmt.setFetchSize(count + 1);
                stmt.setMaxRows((beginIndex * count) + 1);
                return stmt;
            }
        });
        final PreparedStatementCreator psc = preparedStatementCreatorFactory.newPreparedStatementCreator(parameterList);
        final ResultSetExtractor<List<T>> rse = new ResultSetExtractor<List<T>>() {
            public List<T> extractData(final ResultSet rs) throws SQLException {
                if (count > 0) {
                    rs.setFetchSize(count + 1);
                    if (beginIndex > 0) {
                        rs.absolute((beginIndex - 1) * count);
                    }
                }
                rs.setFetchDirection(ResultSet.FETCH_FORWARD);
                final List<T> results = new ArrayList<T>(count + 1);
                for (int rowNumber = 0; rs.next(); ++rowNumber) {
                    if (count > 0 && rowNumber > count) {
                        break;
                    }
                    results.add(mapper.mapRow(rs, rowNumber));
                    rs.last();
                    context.setTotalResults(rs.getRow());
               }
                return results;
            }
        };
        return this.simpleJbcTemplate.query(psc, null, rse);
    }
    

    这里是 PagingQueryContext:

    public class PagingQueryContext implements Serializable {
        private static final long serialVersionUID = -1887527330745224117L;
    
        private Integer beginIndex = 0;
        private Integer count = -1;
        private Integer totalResults = -1;
    
        public PagingQueryContext() {
        }
    
        public Integer getBeginIndex() {
            return beginIndex;
        }
    
        public void setBeginIndex(final Integer beginIndex) {
            this.beginIndex = beginIndex;
        }
    
        public Integer getCount() {
            return count;
        }
    
        public void setCount(final Integer count) {
            this.count = count;
        }
    
        public Integer getTotalResults() {
            return totalResults;
        }
    
        public void setTotalResults(final Integer totalResults) {
            this.totalResults = totalResults;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((beginIndex == null) ? 0 : beginIndex.hashCode());
            result = prime * result + ((count == null) ? 0 : count.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(final Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (!(obj instanceof PagingQueryContext)) {
                return false;
            }
            final PagingQueryContext other = (PagingQueryContext) obj;
            if (beginIndex == null) {
                if (other.beginIndex != null) {
                    return false;
                }
            } else if (!beginIndex.equals(other.beginIndex)) {
                return false;
            }
            if (count == null) {
                if (other.count != null) {
                    return false;
                }
            } else if (!count.equals(other.count)) {
                return false;
            }
            return true;
        }
    
    }
    

    它将获取大小加一,以便您可以查看是否会有更多结果。此外,根据您使用的 JDBC 驱动程序如何实现rs.last(),您可能不想在ResultSetExtractor 中使用该调用并放弃使用totalRows。某些驱动程序可能会在调用 last() 时加载所有数据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-21
      • 2016-11-16
      • 1970-01-01
      • 2016-04-20
      相关资源
      最近更新 更多