Большой список, возвращаемый из запроса SimpleJdbcTemplate - PullRequest
9 голосов
/ 07 июля 2011

здесь моя проблема: в какой-то момент в моей Java-программе я получаю (очень) большой Список событий из базы данных, используя класс SimpleJdbcTemplate из Spring.

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 (или любой другой библиотеки)?

Ура, Vakimshaar.

Ответы [ 4 ]

5 голосов
/ 07 июля 2011

Я думаю, что частью вашей проблемы является то, что ваш запрос выполняется одновременно, и вы получаете результирующий набор в виде большого куска, который урезает память и пропускную способность сети.В дополнение к тому, что вам нужен способ итерации по набору результатов, вам нужен способ получать результаты из базы данных постепенно.Взгляните на этот ответ о отложенной загрузке результатов .Похоже, вы можете установить размер выборки в сочетании с использованием ResultSetExtractor и, возможно, получить желаемое поведение (в зависимости от базы данных).

3 голосов
/ 07 июля 2011

Вы должны создать свой SQL-запрос, чтобы он возвращал ограниченный набор элементов, начиная с определенного числа. Это специфическая для базы данных операция (в Oracle и MySql вы будете манипулировать rownum в той или иной форме). Затем вы повторяете начальный номер увеличения вызова, пока все элементы не будут обработаны.

Пример Oracle

SELECT * FROM ([your query]) WHERE rownum>=[start number] 
                               AND rownum<[start number + chunk size];
2 голосов
/ 07 июля 2011

Если я правильно понимаю, вы хотели бы перебрать набор результатов, но не заинтересованы в построении полного списка результатов.

Просто используйте метод запроса с ResultSetExtractor в качестве аргумента. ResultSetExtractor может использовать ваш преобразователь для преобразования текущей строки в Event. Поместите каждое событие в список, пока не достигнете другого KEY_ID или конца набора результатов, затем перейдите к списку событий и очистите список.

1 голос
/ 07 июля 2011

Может быть, следующий код может быть полезен для вас?

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().

...