【问题标题】:Can you use Springs JdbcTemplate to stream data [duplicate]您可以使用 Springs JdbcTemplate 流式传输数据吗?
【发布时间】:2021-01-08 13:25:05
【问题描述】:

Springs JdbcTemplate 能否用于查询数据库并将结果作为流提供?

【问题讨论】:

标签: java spring kotlin java-stream jdbctemplate


【解决方案1】:

是的 - 但不是开箱即用的。

我发现这篇非常有用的文章介绍了如何做到这一点:Using the Java 8 Stream API with Spring’s JdbcTemplate

受这篇文章的启发,我做了一个改进的实现:

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet;
import org.springframework.stereotype.Component;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

@Slf4j
@Component
@RequiredArgsConstructor
public class QueryStreamer {

  private final NamedParameterJdbcTemplate jdbcTemplate;

  /**
   * Execute query and make result available for a Stream{@literal <T>} consumer
   * @param sql
   * @param parameters
   * @param clazz
   * @param consumer
   * @param <T>
   */
  public <T> void queryForStream(
    String sql,
    MapSqlParameterSource parameters,
    Class<T> clazz,
    java.util.function.Consumer<Stream<T>> consumer
  ) {
    queryForStream(sql, parameters, resultSetStream -> {
      BeanPropertyRowMapper<T> mapper = new TrimmingBeanPropertyRowMapper<>(clazz);
      consumer.accept(resultSetStream.map(r -> mapIt(r, mapper)));
      return null;
    });
  }

  // Build a Stream<ResultSet>
  private void queryForStream(
    String sql,
    MapSqlParameterSource parameters,
    java.util.function.UnaryOperator<Stream<ResultSet>> operator
  ) {
    jdbcTemplate.query(sql, parameters, resultSet -> {
      final ResultSetWrappingSqlRowSet rowSet = new ResultSetWrappingSqlRowSet(resultSet);
      final boolean parallel = false;

      Spliterator<ResultSet> spliterator = Spliterators.spliteratorUnknownSize(new Iterator<>() {
        @Override
        public boolean hasNext() {
          return rowSet.next();
        }

        @Override
        public ResultSet next() {
          return resultSet;
        }
      }, Spliterator.IMMUTABLE);
      return operator.apply(StreamSupport.stream(spliterator, parallel));
    });
  }

  private static <T> T mapIt(ResultSet resultSet, BeanPropertyRowMapper<T> mapper) {
    try {
      return mapper.mapRow(resultSet, 0);
    } catch (SQLException e) {
      throw new RuntimeException(e);
    }
  }

}

这就是你可以在 DAO 中使用它的方式——在 Kotlin 中实现,因为它提供了良好的多行字符串文字支持:

import MyEntity
import QueryStreamer
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.stereotype.Component
import java.util.function.Consumer
import java.util.stream.Stream

@Component
open class MyEntityDAO(private val queryStreamer: QueryStreamer) {

  val sql = """
          SELECT column_1,
                 column_2,
                 column_3
            FROM my_entity_table
           WHERE some_criteria = 'met' 
          """.trimIndent()

  fun streamIt(consumer: Consumer<Stream<MyEntity>>) {
    queryStreamer.queryForStream(sql, MapSqlParameterSource(), MyEntity::class.java, consumer)
  }
}

就是这样 - 玩得开心:-)

【讨论】:

  • 我知道它是 Iterator 接口的非正统实现——因为 next() 依赖于 hasNext() 已经推进了结果集——因此不检查是否推进到末尾。 SonarQube 会抱怨它 - 但效果很好。
  • 使用Spring R2DBC 代替 JdbcTemplate 不是更有意义吗?那是从头开始设计的,不是吗?
  • @Erik 你可能是对的 - R2DBC 已经逃脱了我的注意。它看起来很有趣。但似乎并非所有数据库都有驱动程序。我在 DB2 环境中工作 - 快速谷歌搜索没有找到任何 R2DBC 对 DB2 的支持?无论如何 - 我想将它用于一个特殊情况和较旧的应用程序,它本质上是普通的 JdbcTemplate。但是我可以单独使用 RowCallbackHandler 来实现我的目标:-)
猜你喜欢
  • 1970-01-01
  • 2011-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-15
  • 1970-01-01
  • 1970-01-01
  • 2015-05-05
相关资源
最近更新 更多