Как вернуть поток java8 из набора результатов jdbc

Чтобы наилучшим образом использовать поток java8 и Spring4, я использую Stream API следующим образом для набора результатов JDBC, полученного из Springs jsdbRestTemplate (код сокращен и упрощен):

public <T> T consumeResultStream(
    String query, 
    Function<Stream<String>, T> extractorFunction
) {
    return jdbcTemplate.query(
        query, 
        resultSet -> {
            Spliterator<String> spliterator = 
                Spliterators.spliteratorUnknownSize(
                    new Iterator<String>() {
                      @Override public boolean hasNext() {
                        return !resultSet.isAfterLast();
                      }
                      @Override public String next() {
                        String result = resultSet.getString(0);
                        resultSet.next();
                        return result;
                      }
                    }, 
                    Spliterator.IMMUTABLE);
           resultStream = StreamSupport.stream(
               spliterator, /*parallel*/ false);
       }
       return extractorFunction.apply(resultStream);
    });
}

Кажется, это работает нормально. Клиенты могут использовать потоковый API таким образом, не беспокоясь о классах jdbc.

List<T> myResult = consumeResultStream("SELECT ...", stream -> 
    stream.filter((String s) -> ...)
        .map(String s -> toT(s))
        .collect(toList()));

Однако, когда я выполняю рефакторинг (пытаясь вместо этого предоставить поток клиентским методам), вот так:

    final Stream<String> stream = 
        jdbcTemplate.query(query, resultSet -> {
          // ... same as above
          return resultStream;
        });
    return extractorFunction.apply(stream);

я получил

org.springframework.jdbc.InvalidResultSetAccessException: 
  The object is already closed [90007-199]

Таким образом, кажется, что данные можно прочитать только в методе jdbcTemplate.query(). Есть ли чистый способ обойти это и вернуть ленивый поток элементов, поступающих из БД? Предположим, что материализация результата и потоковая передача невозможна из-за размера результатов (хотя разбиение на страницы может быть лучшим шаблоном).


person tkruse    schedule 05.07.2019    source источник
comment
jdbcTemplate.query(...) инкапсулирует операцию, которая обрабатывает результирующий набор и закрывает его. Он не может вернуть ленивый поток. С другой стороны, когда вы предоставляете оператор SQL и обрабатываете ResultSet вручную, почему бы не работать с Statement или PreparedStatement напрямую? Кроме того, проще реализовать Spliterator, а не Iterator. См. stackoverflow.com/q/32209248/2711488.   -  person Holger    schedule 05.07.2019
comment
Думаю, мой вопрос здесь можно закрыть как дубликат связанного. Теперь я беспокоюсь, разумно ли оставить потоки закрытыми для других.   -  person tkruse    schedule 05.07.2019
comment
Так работают, например, Files.lines() и Files.list().   -  person Holger    schedule 08.07.2019


Ответы (1)


JdbcTemplate не обрабатывает транзакцию после ее вызова, в отличие от функции Spring JPA.
Чтобы не допустить закрытия соединения с БД, откройте транзакцию на стороне клиента, которая манипулирует возвращенным ленивым результатом.
Аннотировать ее с помощью @Transactional является в общем достаточно:

@Transactional
public void findLazyData(){
   Stream<String> result = dataAccessService.find(...);
   // where find() contains the JdbcTemplate invocation
}

Обратите внимание на происхождение пакета: org.springframework.transaction.annotation.Transactional.

person davidxxx    schedule 05.07.2019
comment
Я пробовал, но все тот же результат. Я проверил, что транзакции и тесты настроены правильно, выполняя TransactionAspectSupport.currentTransactionStatus() в методе, который возвращает действительный объект (если я удаляю аннотации, я получаю исключение NoTransactionException). - person tkruse; 05.07.2019